Skip to main content
Glama

Server Details

Connect your AI to your Well financial data - invoices, companies, contacts.

If you are the author of this connector, you can claim ownership with GitHub, an HTTP challenge, or a DNS record. Claimed connector authors can inspect health checks, view analytics, and manage their listing.
Status
Healthy
Uptime
99.8% over 38 days
Last Tested
Transport
Streamable HTTP · MCP 2025-11-25
URL

TDQS

A4.1/5.0

Scored across 87 tools

Disambiguation4/5

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.

Naming Consistency4/5

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.

Tool Count2/5

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.

Completeness4/5

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 tools
well_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.

ParametersJSON Schema
NameRequiredDescriptionDefault
valueYesChannel value — fill the field(s) for the chosen channel
parentYesParent record type: company or person
channelYesChannel to add: email | phone | web_link | location
parent_idYesUUID of the parent company or person
workspace_idNoTarget 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_idNoThe 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_keyNoOptional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation.

Output Schema

ParametersJSON Schema
NameRequiredDescription
errorNo
parentNo
channelNo
successYes
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A5/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters5/5

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.

Purpose5/5

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

States a specific verb and resource: 'Add a 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.

Usage Guidelines5/5

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 accountA
Destructive
Inspect

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
ownershipNoWhether the workspace owns the account: "workspace", "counterparty", or "unknown".
account_idYesThe UUID of the account to assign (required)
company_idNoThe company that owns the account, in the same workspace. `null` detaches it.
workspace_idNoTarget 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_idNoThe 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_keyNoOptional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation.

Output Schema

ParametersJSON Schema
NameRequiredDescription
errorNo
successYes
ownershipNo
account_idNo
company_idNo
company_nameNo
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.6/5.0
Behavior5/5

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.

Conciseness4/5

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.

Completeness5/5

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.

Parameters4/5

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

Schema coverage is 100%, but the description adds 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.

Purpose5/5

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

The description opens with a specific verb and resource: '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.

Usage Guidelines4/5

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 ownersA
Destructive
Inspect

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).

ParametersJSON Schema
NameRequiredDescriptionDefault
workspace_idNoTarget 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_idNoThe 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_keyNoOptional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation.
transaction_idsYesThe missing-invoice transactions to assign, from well_list_missing_invoice_owners.
owner_person_idsYesThe workspace people who together own these transactions' missing invoices; an empty array clears the owners.

Output Schema

ParametersJSON Schema
NameRequiredDescription
errorNo
successYes
owner_countNoHow many people own each of those transactions after the write.
refusal_reasonNoThe 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_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
refusal_detailsNoStructured 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_idsNoThe distinct transactions whose owner set the write replaced.
owner_person_idsNoThe explicit owner set written to every named transaction; empty when the owners were cleared.
transaction_countNoHow many distinct transactions the write touched.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.9/5.0
Behavior5/5

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.

Conciseness4/5

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.

Completeness5/5

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.

Parameters5/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
claim_tokenYesThe 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_idNoTarget 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_idNoThe 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_keyNoOptional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation.

Output Schema

ParametersJSON Schema
NameRequiredDescription
errorNo
failedYesFiles that were claimed but did not ingest. Their bytes are gone; ask the user for exactly these by name.
drop_idNoThe drop this token belonged to. Safe to quote; the token is not.
successYes
documentsYesOne entry per file that reached the import pipeline.
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A5/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters5/5

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

Schema coverage is 100%, so the baseline is 3, but the description adds 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.

Purpose5/5

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.

Usage Guidelines5/5

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
nameYesCompany name (required)
descriptionNoBrief company description
workspace_idNoTarget 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_idNoThe 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_keyNoOptional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation.

Output Schema

ParametersJSON Schema
NameRequiredDescription
nameNo
errorNo
successYes
company_idNo
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.5/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters3/5

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.

Purpose5/5

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

The description opens with a clear verb+resource ('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.

Usage Guidelines4/5

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

The description provides explicit when-to-use guidance ('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.

ParametersJSON Schema
NameRequiredDescriptionDefault
registry_refYesThe registry ref an earlier well_search_company_registry hit carried as its `id`.
workspace_idNoTarget 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_idNoThe 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_keyNoOptional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation.

Output Schema

ParametersJSON Schema
NameRequiredDescription
errorNo
successYes
candidateYesThe minted candidate, projected as well_get_own_company shows a candidate; null when linked to an existing child.
workspace_idYesThe existing company workspace when linked_to_existing_child is true; null otherwise.
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere 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_childYesTrue when the picked company already has a confirmed company workspace; its id is in workspace_id.

TDQS

A4.9/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness5/5

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

Given the tool's complexity—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.

Parameters4/5

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

Schema coverage is 100%, so the baseline is 3. The description adds value by 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.

Purpose5/5

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

The description opens with a precise verb 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.

Usage Guidelines5/5

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

The description gives explicit when-to-use guidance: 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 workspaceA
Idempotent
Inspect

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
candidate_idYesThe candidate id returned by well_create_company_candidate.
workspace_idNoTarget 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_idNoThe 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_keyNoOptional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation.

Output Schema

ParametersJSON Schema
NameRequiredDescription
nameNoThe company workspace's name, derived from the candidate.
errorNo
successYes
workspace_idNoThe created (or existing) company workspace's id.
own_company_idNoThe public id of the company anchored as the workspace's own.
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
already_anchoredNoTrue 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_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.7/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters3/5

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

Schema description coverage is 100%, so baseline is 3. The description adds 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.

Purpose5/5

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

States a specific verb and resource: 'Create 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.

Usage Guidelines5/5

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
invoice_idYes
workspace_idNoTarget 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_idNoThe 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_keyNoOptional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation.

Output Schema

ParametersJSON Schema
NameRequiredDescription
fileNo
errorNo
successYes
invoice_idNo
document_idNo
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
reference_numberNo
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.4/5.0
Behavior5/5

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

Annotations only state readOnlyHint=false and destructiveHint=false; 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.

Conciseness4/5

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.

Completeness5/5

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

Given the tool's moderate complexity, the 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.

Parameters3/5

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.

Purpose5/5

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

The description opens with a specific verb and resource: '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.

Usage Guidelines4/5

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
issuerYes
statusNoThe 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.
totalsNo
currencyYesISO 4217 (3 letters).
due_dateNoISO 8601 YYYY-MM-DD.
receiverYes
issue_dateYesISO 8601 YYYY-MM-DD.
line_itemsYes
workspace_idNoTarget 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_meansNo
conversation_idNoThe 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_keyNoOptional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation.
reference_numberYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
errorNo
successYes
invoice_idNo
document_idNo
payment_meansNo
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
reference_numberNo
invoice_item_countNo
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.6/5.0
Behavior4/5

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.

Conciseness4/5

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.

Completeness5/5

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.

Parameters4/5

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.

Purpose5/5

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

The description clearly states the tool creates 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.

Usage Guidelines5/5

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
emailNo
phoneNo
job_titleNoJob title
last_nameNoLast name (optional)
first_nameYesFirst name (required)
workspace_idNoTarget 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_idNoThe 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_keyNoOptional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation.

Output Schema

ParametersJSON Schema
NameRequiredDescription
errorNo
successYes
full_nameNo
person_idNo
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.2/5.0
Behavior4/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters3/5

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.

Purpose5/5

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

The description opens with a specific action and 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.

Usage Guidelines4/5

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
workspace_idNoTarget 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_idNoThe 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_keyNoOptional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation.

Output Schema

ParametersJSON Schema
NameRequiredDescription
errorNo
tokenNo
successYes
upload_urlNo
document_idNoThe pre-allocated document id — poll well_get_statement_import_result with it.
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
expires_in_secondsNo
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.7/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters3/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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 companyA
Destructive
Inspect

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
company_idYesThe UUID of the company to delete (required)
workspace_idNoTarget 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_idNoThe 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_keyNoOptional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation.

Output Schema

ParametersJSON Schema
NameRequiredDescription
errorNo
successYes
company_idNo
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.4/5.0
Behavior5/5

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.

Conciseness4/5

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.

Completeness5/5

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.

Parameters3/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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 invoiceA
Destructive
Inspect

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
invoice_idYesThe UUID of the invoice to delete
workspace_idNoTarget 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_idNoThe 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_keyNoOptional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation.

Output Schema

ParametersJSON Schema
NameRequiredDescription
errorNo
successYes
invoice_idNo
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A3.7/5.0
Behavior4/5

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.

Conciseness4/5

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.

Completeness4/5

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.

Parameters3/5

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

Schema description coverage is 100%, so the baseline is 3. The description 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.

Purpose5/5

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.

Usage Guidelines2/5

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 personA
Destructive
Inspect

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
person_idYesThe UUID of the person to delete (required)
workspace_idNoTarget 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_idNoThe 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_keyNoOptional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation.

Output Schema

ParametersJSON Schema
NameRequiredDescription
errorNo
successYes
person_idNo
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.5/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters3/5

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

Schema description coverage is 100%, and the schema already documents 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.

Purpose5/5

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

The description states a specific verb and resource ('Delete a 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.

Usage Guidelines4/5

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 collectionA
Idempotent
Inspect

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
workspace_idNoTarget 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_idNoThe 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_keyNoOptional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation.
counterparty_company_idsYesCounterparty companies to queue collection for, from well_list_missing_invoices.

Output Schema

ParametersJSON Schema
NameRequiredDescription
errorNo
skippedNo
successYes
enqueuedNo
skipped_countNo
enqueued_countNoRows in enqueued — created or reused.
refusal_reasonNoThe WellError code when the write is refused.
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
already_active_countNoOf enqueued_count, how many reused an existing task rather than creating one.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.9/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness5/5

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

Given the tool's complexity 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.

Parameters4/5

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

Schema description coverage is 100%, so the baseline is 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.

Purpose5/5

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

The description opens with a specific verb and resource: '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.

Usage Guidelines5/5

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 settingsA
Read-only
Inspect

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
workspace_idNoTarget 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_idNoThe 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

ParametersJSON Schema
NameRequiredDescription
errorNo
successYes
settingsNo
next_stepNoWhat to do with the card this result renders. Added by the dispatcher when the card asks for a click.
suggestionsNo
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
resolved_workspaceNoThe workspace that answered, when the caller named none and the token authorizes several.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.6/5.0
Behavior5/5

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.

Conciseness4/5

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.

Completeness5/5

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.

Parameters3/5

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

Schema description coverage is 100%, so the schema already documents `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.

Purpose5/5

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

The description states a specific verb ('Read'), 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.

Usage Guidelines5/5

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 coverageA
Read-only
Inspect

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
qNoName search across the full catalog (e.g. a specific bank). Omit for the curated, matched-first view.
kindNoScope 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.
limitNoMax connectors to return (1-100, default 50).
offsetNoNumber of connectors to skip, for paging (default 0).
countryNoThe 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_idNoTarget 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_selectionNoScope 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_idNoThe 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_countsNoAdd 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

ParametersJSON Schema
NameRequiredDescription
errorNo
limitNoThe page size that was REQUESTED. The catalog may return fewer.
scopeNoWhat 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.
totalNoEvery connector matching the query, across all pages — NOT the length of `connectors`.
offsetNoHow many catalog rows this page skipped.
successYes
row_countNoHow 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.
connectorsNo
page_countNoThe 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_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
install_all_urlYesOne 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_workspaceNoThe workspace that answered, when the caller named none and the token authorizes several.
install_all_omittedYesThe 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_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.
unsent_document_countsNoEvery 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_filteredNoHow 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

A4.7/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters3/5

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.

Purpose5/5

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

The description opens with a specific verb-resource pair: 'Read 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.

Usage Guidelines5/5

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

The description gives explicit when-to-use 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 detailsA
Read-only
Inspect

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
company_idYesThe customer's public company UUID (the `company_id` field on a companies record).
workspace_idNoTarget 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_idNoThe 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

ParametersJSON Schema
NameRequiredDescription
errorNo
hintsNo
fieldsYes
successYes
customerYes
error_reasonNo
customer_kindNo
connectors_urlNo
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
unstorable_fieldsYes
resolved_workspaceNoThe workspace that answered, when the caller named none and the token authorizes several.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.6/5.0
Behavior5/5

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.

Conciseness4/5

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.

Completeness5/5

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.

Parameters3/5

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

Schema coverage is 100%, so the schema already documents 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.

Purpose5/5

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.

Usage Guidelines5/5

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 tokensA
Read-only
Inspect

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
workspace_idNoTarget workspace. Optional: this tool describes the token itself rather than one workspace's data, so omitting it returns the same answer.
conversation_idNoThe 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

ParametersJSON Schema
NameRequiredDescription
errorNo
fontsYes
hintsNo
shapeYes
colorsYes
seriesYes
successYes
color_schemeNo
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.8/5.0
Behavior5/5

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.

Conciseness4/5

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.

Completeness5/5

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.

Parameters4/5

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

Schema description coverage is 100%, so both parameters are 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.

Purpose5/5

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.

Usage Guidelines5/5

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)A
Read-only
Inspect

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
idYesThe entity's public UUID (the value of its *_id field, e.g. company_id)
rootYesEntity type, e.g. companies | people | invoices | transactions
depthNoRelation-nesting boundary 1-3 (default 1).
workspace_idNoTarget 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_idNoThe 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

ParametersJSON Schema
NameRequiredDescription
depthNo
errorNo
foundYes
entityYes
successYes
columnMetaNoPer-column field meaning ({context, enrichment}) for documented columns — read this to interpret the entity's values.
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
fields_selectedNo
resolved_workspaceNoThe workspace that answered, when the caller named none and the token authorizes several.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.9/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters4/5

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

Schema coverage is 100%, so the baseline is 3, but the description 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.

Purpose5/5

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.

Usage Guidelines5/5

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 graphA
Read-only
Inspect

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
min_degreeNoDrop nodes with fewer than this many connections. Raise it to thin a dense workspace down to its hubs; 0 keeps every node.
company_capNoCap the number of company nodes drawn. Omit for the server's unbounded default.
perspectiveNoWhich 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_windowNoHow far back to reach for the underlying records. Defaults to the server's own default when omitted.
workspace_idNoTarget 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_idNoThe 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

ParametersJSON Schema
NameRequiredDescription
errorNo
successYes
edge_countNoConnections between the shipped nodes.
node_countNoNodes the server actually shipped, after every filter.
company_capNoThe company cap the server applied, when one was.
failed_countNoOpen work that cannot progress on its own: stuck tasks and errored connectors.
at_company_capNoTrue 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_idNoThe workspace's own company node, when one is resolved.
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
processing_countNoOpen work that may still change the scene: queued or running tasks, plus connectors mid-sync.
resolved_workspaceNoThe workspace that answered, when the caller named none and the token authorizes several.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.5/5.0
Behavior4/5

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

Annotations already declare readOnlyHint=true and 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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters3/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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

It explicitly states when to use: 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 holdingsA
Read-only
Inspect

Get the live holdings/positions (what's currently held and its value) for a connected Plaid investment account — brokerage, IRA, 401k, etc.

WORKFLOW:

  1. well_list_connectors() → pick the ENABLED Plaid connector (connection_status: "enabled") and read its workspace_connector_id directly off the row.

  2. 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.

ParametersJSON Schema
NameRequiredDescriptionDefault
workspace_idNoTarget 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_idNoThe 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_idYesThe connected Plaid provider's workspace_connector_id (from well_list_connectors).

Output Schema

ParametersJSON Schema
NameRequiredDescription
errorNo
successYes
holdingsNo
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
resolved_workspaceNoThe workspace that answered, when the caller named none and the token authorizes several.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.9/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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 companyA
Read-only
Inspect

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
workspace_idNoTarget 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_idNoThe 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

ParametersJSON Schema
NameRequiredDescription
errorNo
anchorYes
successYes
candidatesYes
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
resolved_workspaceNoThe workspace that answered, when the caller named none and the token authorizes several.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.7/5.0
Behavior5/5

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

Annotations already declare readOnlyHint=true and 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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters3/5

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

Schema description coverage is 100%, so the schema already documents both parameters 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.

Purpose5/5

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

The description states a specific verb and resource: '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.

Usage Guidelines5/5

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 schemaA
Read-only
Inspect

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:

  1. Call well_get_schema(root) to see available fields

  2. Pick the fields you need for your task (typically 5-15)

  3. 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"]

ParametersJSON Schema
NameRequiredDescriptionDefault
rootNoEntity 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.
depthNoRelationship depth: 0=scalars only, 1=direct relations (default), 2=nested, 3=level-3 graph
workspace_idNoTarget workspace. Optional: this tool describes the token itself rather than one workspace's data, so omitting it returns the same answer.
conversation_idNoThe 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

ParametersJSON Schema
NameRequiredDescription
rootNo
errorNo
rootsNo
totalNo
fieldsNo
successYes
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.6/5.0
Behavior5/5

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.

Conciseness4/5

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.

Completeness5/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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 digestA
Read-onlyIdempotent
Inspect

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
markNoAdvance 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_idNoTarget 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_idNoThe 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

ParametersJSON Schema
NameRequiredDescription
errorNo
errorsYes
reasonYes
skillsYesThe whole Well skill roster, the same entries well_search_skill returns. The step a click names is loaded from it with well_get_skill.
recordsYes
successYes
boundaryYesWhere the window starts: the person's read cursor, their previous sign-in, or neither. Null on a refusal.
since_atYesThe instant the window starts from, set only when a previous sign-in bounded it.
situationYesWhere the workspace stands now: its connected tools, its open month, and what that month still owes.
time_zoneYesThe IANA time zone the label was written in, from the workspace's settings.
truncatedYesTrue when events sit past the read cap of 5000, so the counts cover part of the tail only.
skills_runYes
since_at_labelYesThe 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_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
roster_readableYesFalse 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_stepsYesThe 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_sessionYes
resolved_workspaceNoThe workspace that answered, when the caller named none and the token authorizes several.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4/5.0
Behavior1/5

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.

Conciseness4/5

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.

Completeness5/5

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

For a 3-parameter tool with 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.

Parameters4/5

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

Schema coverage is 100%, so the baseline is 3, but the description adds meaningful semantics 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.

Purpose5/5

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

The description states a specific verb and resource: it 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.

Usage Guidelines5/5

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

The description gives explicit when-to-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 skillA
Read-only
Inspect

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
skillYesThe id of the skill to load, as well_search_skill lists it.
workspace_idNoTarget workspace. Optional: this tool describes the token itself rather than one workspace's data, so omitting it returns the same answer.
conversation_idNoThe 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

ParametersJSON Schema
NameRequiredDescription
bytesYes
errorNo
skillYes
reasonYes
successYes
documentYes
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.7/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters3/5

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

Schema description coverage is 100%, so the baseline is 3 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.

Purpose5/5

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

The description opens with a specific verb and resource: 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.

Usage Guidelines5/5

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 resultA
Read-only
Inspect

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. records lists the minted transactions only — matched or ambiguous lines link an existing transaction and are excluded; graph is the frozen record graph for the same snapshot; records_url opens the workspace's transactions table.

This tool reads only — it changes nothing.

ParametersJSON Schema
NameRequiredDescriptionDefault
document_idYesThe document_id well_create_statement_upload returned — pre-allocated at mint, before the upload lands.
workspace_idNoTarget 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_idNoThe 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

ParametersJSON Schema
NameRequiredDescription
hintNo
errorNo
graphNoThe 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.
phaseNo
reasonNo
statusNo
recordsNoTransactions 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.
successYes
document_idNo
imported_atNo
records_urlNoLogin-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_countNoLines minted as new transactions. Absent means the row predates count tracking — treat as unknown, never as 0.
review_countNoLines skipped as an ambiguous cross-connector match, pending review. Absent means the row predates count tracking — treat as unknown, never as 0.
matched_countNoLines linked to an existing cross-connector transaction. Absent means the row predates count tracking — treat as unknown, never as 0.
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
resolved_workspaceNoThe workspace that answered, when the caller named none and the token authorizes several.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
already_present_countNoLines 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_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.
counterparties_pendingNoWhether 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_idNo

TDQS

A4.7/5.0
Behavior5/5

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.

Conciseness5/5

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

The description is long but every sentence earns its place: core purpose, 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.

Completeness5/5

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.

Parameters3/5

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.

Purpose5/5

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

The description opens with a specific verb and resource: 'Read the 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.

Usage Guidelines5/5

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 statusA
Read-only
Inspect

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. Needs from (inclusive) and to (EXCLUSIVE), both YYYY-MM-DD.

  • unposted_transactions — a period's categorized rows still missing the ledger account they would post to. Needs fiscal_year and fiscal_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 any well_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. Needs periods, 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.

ParametersJSON Schema
NameRequiredDescriptionDefault
toNo`uncategorized_window` only: the day AFTER the window's last, exclusive.
fromNo`uncategorized_window` only: the window's first day, inclusive.
periodsNo`counterparties_to_categorize` only: the calendar months to check, the same list the card takes. Every month must have ended.
worklistYesWhich repair gate to check. Each one names its own required scope in this tool's description.
fiscal_yearNo`unposted_transactions` only: the period's fiscal year.
workspace_idNoTarget 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_periodNo`unposted_transactions` only: the fiscal period, 1-12 for a month; 13 is the adjustment period and resolves to no rows.
conversation_idNoThe 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

ParametersJSON Schema
NameRequiredDescription
openNoWhether the worklist still holds a row. Absent when `success` is false — an unknown gate, not a clear one.
countNoHow 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.
errorNo
successYes
worklistYesThe gate that was checked, echoed back.
card_toolYesThe tool that draws this worklist's repair card. Call it only when `open` is true.
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
resolved_workspaceNoThe workspace that answered, when the caller named none and the token authorizes several.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.8/5.0
Behavior5/5

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.

Conciseness4/5

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.

Completeness5/5

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.

Parameters4/5

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

Schema coverage is 100%, so the baseline is 3, but the description adds real 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.

Purpose5/5

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.

Usage Guidelines5/5

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
notifyNoSend 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.
targetYesWhere the invites land: this workspace, or a workspace group.
invitesYesThe people to invite, 1 to 20 per call.
workspace_idNoTarget 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_idNoThe 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_keyNoOptional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation.

Output Schema

ParametersJSON Schema
NameRequiredDescription
errorNo
resultsYesOne result per invite, in the order they were sent.
successYes
workspace_idNo
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.9/5.0
Behavior5/5

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

The description goes well beyond the readOnly/destructive 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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters4/5

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

Schema coverage is 100%, so the baseline is 3. The description adds 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.

Purpose5/5

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.

Usage Guidelines5/5

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 toolA
Destructive
Inspect

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:

  1. 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.

  2. well_list_connector_tools({ workspace_connector_id }) → the live tool names + input schemas that connection actually exposes right now.

  3. 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.

ParametersJSON Schema
NameRequiredDescriptionDefault
argsNoArguments object passed straight to the provider tool. Omit if the tool takes none.
toolYesThe provider tool name to run (one of the connector's available_tools).
workspace_idNoTarget 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_idNoThe 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_keyNoOptional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation.
workspace_connector_idYesThe connected provider's workspace_connector_id (from well_list_connectors).

Output Schema

ParametersJSON Schema
NameRequiredDescription
toolNo
errorNo
resultNo
successYes
error_codeNo
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.5/5.0
Behavior4/5

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.

Conciseness4/5

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.

Completeness4/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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 balancesA
Read-only
Inspect

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 when own_company_id is set AND the row names a different company. A row with no company_id is trusted, because a connector tags a row before any holder is known; so is a row naming a company while own_company_id is still null, 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 when company_id equals own_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.

ParametersJSON Schema
NameRequiredDescriptionDefault
months_backNoHow many complete month ends to carry per account, oldest first. Omit for the current reading alone.
workspace_idNoTarget 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_idNoThe 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

ParametersJSON Schema
NameRequiredDescription
rowsYes
errorNo
partialYes
successYes
base_currencyYes
own_company_idYes
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
unreadable_rowsYes
resolved_workspaceNoThe workspace that answered, when the caller named none and the token authorizes several.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.8/5.0
Behavior5/5

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.

Conciseness4/5

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.

Completeness5/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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 companyA
Read-only
Inspect

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoMax accounts to return (default 200).
workspace_idNoTarget 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_searchNoNarrows 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_idNoThe 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

ParametersJSON Schema
NameRequiredDescription
errorNo
recordsYes
successYes
returnedNo
truncatedNo
own_company_idNoThe company that IS the workspace; null when no anchor is set.
company_catalogNoHow 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_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
resolved_workspaceNoThe workspace that answered, when the caller named none and the token authorizes several.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.7/5.0
Behavior5/5

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.

Conciseness3/5

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.

Completeness5/5

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.

Parameters4/5

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

Schema description coverage is 100%, so the baseline is 3; the description adds 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.

Purpose5/5

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.

Usage Guidelines5/5

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 exemptionsA
Read-only
Inspect

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
toYesEXCLUSIVE end of the window, ISO-8601 — the first instant after the last month you want.
fromYesInclusive start of the window, ISO-8601 (e.g. 2026-06-01).
conventionYesWhich 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_idNoTarget 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_idNoThe 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

ParametersJSON Schema
NameRequiredDescription
errorNo
totalYesThe sum of groups[].amount, and nothing else.
groupsYes
windowYes
partialYesTrue 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.
successYes
currencyYesThe one currency every row shares; empty only when the window held no row.
next_stepNoWhat to do with the card this result renders. Added by the dispatcher when the card asks for a click.
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
unreadable_rowsYesRows in the window whose amount could not be read. They are in no figure here, including the unclassified one.
resolved_workspaceNoThe workspace that answered, when the caller named none and the token authorizes several.
unclassified_countYes
unclassified_amountYesOutflow 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_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.6/5.0
Behavior5/5

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.

Conciseness4/5

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.

Completeness5/5

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.

Parameters4/5

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

Schema coverage is 100%, so the baseline is 3, but the description adds real 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.

Purpose5/5

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

Opens with a specific verb and resource: '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.

Usage Guidelines4/5

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 typeA
Read-only
Inspect

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
workspace_idNoTarget 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_idNoThe 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

ParametersJSON Schema
NameRequiredDescription
errorNo
groupsYes
partialYes
successYes
next_stepNoWhat to do with the card this result renders. Added by the dispatcher when the card asks for a click.
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
folded_duplicatesYes
excluded_not_ownedYes
resolved_workspaceNoThe workspace that answered, when the caller named none and the token authorizes several.
unreadable_balancesYes
unreadable_currencyYes
unsettled_ownershipYes
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.6/5.0
Behavior5/5

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

Annotations only declare readOnlyHint=true 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.

Conciseness4/5

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.

Completeness5/5

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.

Parameters3/5

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

Schema description coverage is 100%, so the baseline is 3. The description 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.

Purpose5/5

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

The description opens with a specific verb and resource: 'List the 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.

Usage Guidelines5/5

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 connectorsA
Read-only
Inspect

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
qNoName search across the full catalog (e.g. a specific bank). Omit for the curated, matched-first view.
kindNoScope 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.
limitNoMax connectors to return (1-100, default 50).
titleNoHeading 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.
offsetNoNumber of connectors to skip, for paging (default 0).
countryNoThe 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.
subtitleNoSupporting 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_idNoTarget 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_selectionNoScope 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_idNoThe 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_countsNoAdd 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

ParametersJSON Schema
NameRequiredDescription
errorNo
limitNoThe page size that was REQUESTED. The catalog may return fewer.
scopeNoWhat 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.
totalNoEvery connector matching the query, across all pages — NOT the length of `connectors`.
offsetNoHow many catalog rows this page skipped.
successYes
next_stepNoWhat to do with the card this result renders. Added by the dispatcher when the card asks for a click.
row_countNoHow 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.
connectorsNo
page_countNoThe 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_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
install_all_urlYesOne 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_workspaceNoThe workspace that answered, when the caller named none and the token authorizes several.
install_all_omittedYesThe 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_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.
unsent_document_countsNoEvery 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_filteredNoHow 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

A4.9/5.0
Behavior5/5

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.

Conciseness4/5

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.

Completeness5/5

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.

Parameters5/5

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

Schema description coverage is 100%, so the baseline is 3, but the description adds 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.

Purpose5/5

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

Description opens with a specific verb and resource: '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.

Usage Guidelines5/5

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 toolsA
Read-only
Inspect

Discover the actions a connected provider exposes (e.g. "what can I do with Attio?").

WORKFLOW:

  1. well_list_connectors() → pick the ENABLED provider (connection_status: "enabled") and read its workspace_connector_id directly off the row.

  2. well_list_connector_tools({ workspace_connector_id }) → the actions that provider offers (name + description + input schema).

  3. 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.

ParametersJSON Schema
NameRequiredDescriptionDefault
workspace_idNoTarget 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_idNoThe 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_idYesThe connected provider's workspace_connector_id (from well_list_connectors).

Output Schema

ParametersJSON Schema
NameRequiredDescription
errorNo
toolsNo
totalNo
statusNo
successYes
usage_notesNo
reconnect_urlNo
connector_slugNo
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
resolved_workspaceNoThe workspace that answered, when the caller named none and the token authorizes several.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.7/5.0
Behavior5/5

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

Annotations already declare readOnlyHint=true and 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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters3/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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 counterpartiesA
Read-only
Inspect

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 carrying tx_count, base_total_amount in base_currency, and suggested_retrieval. Every month must have ended.

  • periods PLUS uncategorized_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: true alone: a WORKSPACE-WIDE sweep for every counterparty that carries no category, no month involved. Returns 50 rows per page plus total_count; tx_count, base_total_amount and suggested_retrieval are null because the call names no period. When next_cursor is not null the sweep has more counterparties: call again with cursor set 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: cursor is refused beside periods.

  • missing_ledger_only: true alone: 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 in ledger_rows, not rows, each carrying needs_payable/needs_receivable and 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 with well_update_company({ account_payable_default_id | account_receivable_default_id }); read the account ids with well_list_ledger_accounts. It returns the first 500 counterparties needing a default, so a worklist that fills 500 (total_count equal to row_count at 500) is a FLOOR: assign those and read the scope again for the rest. It is its own scope — never combine it with periods, uncategorized_only, or cursor.

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
cursorNoThe 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.
periodsNoThe calendar months whose counterparties to list, 1-12. Each month costs one separate read of that month's spend. Duplicates are refused.
workspace_idNoTarget 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_idNoThe 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_onlyNoKeep 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_onlyNoA 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

ParametersJSON Schema
NameRequiredDescription
modeNoWhich 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`.
rowsYes
errorNo
hintsNo
successYes
next_stepNoWhat to do with the card this result renders. Added by the dispatcher when the card asks for a click.
row_countNo
ledger_rowsNo
next_cursorNoThe 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_countNoCounterparties 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_idNo
base_currencyNo
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
periods_coveredNoThe months the result covers, oldest first.
categorized_countNoCOUNTERPARTIES 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_workspaceNoThe workspace that answered, when the caller named none and the token authorizes several.
uncategorized_onlyNoWhether `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_countNoCOUNTERPARTIES 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_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.9/5.0
Behavior5/5

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.

Conciseness4/5

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.

Completeness5/5

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.

Parameters5/5

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.

Purpose5/5

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

States a specific verb+resource — 'List the 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.

Usage Guidelines5/5

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 accountsA
Read-only
Inspect

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoMax accounts to return; the chart is capped either way.
workspace_idNoTarget 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_idNoThe 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

ParametersJSON Schema
NameRequiredDescription
errorNo
successYes
accountsYes
returnedNo
truncatedNo
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
resolved_workspaceNoThe workspace that answered, when the caller named none and the token authorizes several.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.8/5.0
Behavior5/5

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

The description goes well beyond the readOnlyHint, 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.

Conciseness4/5

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.

Completeness5/5

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.

Parameters4/5

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

Schema description coverage is 100%, so the baseline is 3. The description adds 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.

Purpose5/5

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

The description clearly states the tool lists 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.

Usage Guidelines5/5

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 candidatesA
Read-only
Inspect

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. Set include_detected false 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.

ParametersJSON Schema
NameRequiredDescriptionDefault
person_idsNoPerson ids to resolve with their membership state (the `provided` source).
fiscal_yearNoFiscal year (the calendar year the workspace's fiscal year STARTED in). `from_assigned_gaps` only.
workspace_idNoTarget 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_yearNoCalendar year, e.g. 2026. `from_assigned_gaps` only.
fiscal_periodNoFiscal period, 1-12. `from_assigned_gaps` only, paired with `fiscal_year`. The adjustment period (13) is refused.
calendar_monthNoCalendar month, 1 = January … 12 = December. `from_assigned_gaps` only, paired with `calendar_year`.
conversation_idNoThe 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_detectedNoOmit the detected same-domain teammates when false. Defaults to true.
from_assigned_gapsNoResolve 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

ParametersJSON Schema
NameRequiredDescription
errorNo
rolesYesThe assignable roles, `admin` or `member`, each with a one-line hint.
successYes
targetsYesWhere an invite can land: this workspace, plus any workspace group the caller belongs to.
next_stepNoWhat to do with the card this result renders. Added by the dispatcher when the card asks for a click.
candidatesYes
me_person_idYesThe caller's own person id, so the card never offers to invite them.
workspace_idNo
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
resolved_workspaceNoThe workspace that answered, when the caller named none and the token authorizes several.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A5/5.0
Behavior5/5

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

Annotations already declare readOnlyHint=true, and the description 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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters5/5

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.

Purpose5/5

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

The description states a specific verb and resource: '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.

Usage Guidelines5/5

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 ownersA
Read-only
Inspect

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
periodsNoSeveral calendar months in one call, 1-12. Each month costs one separate read, so name only the months you need. Duplicates are refused.
fiscal_yearNoFiscal year (the calendar year the workspace's fiscal year STARTED in).
workspace_idNoTarget 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_yearNoCalendar year, e.g. 2026.
fiscal_periodNoFiscal period, 1-12. The adjustment period (13) is refused: it has no calendar month.
calendar_monthNoCalendar month, 1 = January … 12 = December.
conversation_idNoThe 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

ParametersJSON Schema
NameRequiredDescription
errorNo
sampledNoTrue 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.
successYes
next_stepNoWhat to do with the card this result renders. Added by the dispatcher when the card asks for a click.
row_countNoTransaction rows returned in `transactions` (a bounded sample per counterparty).
me_person_idNoThe calling person, against which each row's `bucket` is computed; null when the token carries no person.
transactionsYesThe missing-invoice lines, with no owner set first, then the caller's own, then those owned only by others.
base_currencyNo
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
periods_coveredNoThe months the result covers, oldest first.
transaction_countNoTotal missing-invoice transactions the window holds across all counterparties, sampled or not.
no_owner_set_countNoReturned rows with no transaction owner set.
resolved_workspaceNoThe workspace that answered, when the caller named none and the token authorizes several.
assigned_to_me_countNoReturned rows whose transaction owner set includes the caller.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
transactions_omittedNoMissing-invoice transactions the window holds beyond the returned sample (`transaction_count` − `row_count`).
conversation_id_sourceNoWhere 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_countNoReturned rows whose transaction owner set includes only other people.

TDQS

A4.8/5.0
Behavior5/5

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.

Conciseness4/5

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.

Completeness5/5

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

Given the tool's complexity, 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.

Parameters4/5

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

Schema description coverage is 100%, so the schema already documents 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.

Purpose5/5

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

The description states a specific verb ('List'), 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.

Usage Guidelines5/5

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

The description gives explicit when-to-use ('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 invoicesA
Read-only
Inspect

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).

ParametersJSON Schema
NameRequiredDescriptionDefault
periodsNoSeveral calendar months in one call, 1-12. Each month costs one separate read, so name only the months you need. Duplicates are refused.
fiscal_yearNoFiscal year (the calendar year the workspace's fiscal year STARTED in).
workspace_idNoTarget 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_yearNoCalendar year, e.g. 2026.
fiscal_periodNoFiscal period, 1-12. The adjustment period (13) is refused: it has no calendar month.
calendar_monthNoCalendar month, 1 = January … 12 = December.
conversation_idNoThe 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

ParametersJSON Schema
NameRequiredDescription
rowsYesThe 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.
errorNo
hintsNo
monthsNoPer-month totals, oldest first.
successYes
next_stepNoWhat to do with the card this result renders. Added by the dispatcher when the card asks for a click.
row_countNoRows in `rows`, which is the DISTINCT counterparties the call found. Never the sum of the months' own `row_count`.
fiscal_yearNoPresent only when the call named exactly one month.
group_countNoGroups the reads returned before the card's projection.
period_labelNoHuman-readable label of the period, e.g. "June 2026". Present only when the call named one month.
workspace_idNo
base_currencyNo
calendar_yearNoPresent only when the call named exactly one month.
fiscal_periodNoPresent only when the call named exactly one month.
calendar_monthNoPresent only when the call named exactly one month.
dropped_groupsNo
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
periods_coveredNoThe months the result covers, oldest first.
periods_requestedNoHow many calendar months the call named.
transaction_countNoEvery transaction missing its invoice, across all groups and all months.
resolved_workspaceNoThe workspace that answered, when the caller named none and the token authorizes several.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A5/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness5/5

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

Given the tool's complexity, 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.

Parameters5/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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

It explicitly states when to use the tool ('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 periodsA
Read-only
Inspect

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_invoices would 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. analysis counts on executed_at, the same column well_sum_transactions ranges, 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. close and collect count 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 analysis rather 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 analysis list 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 a close or collect list.

  • 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". days is 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 a purpose: "analysis" call, whatever that month's invoice_state, because the reporting axis paints no day. On that purpose an empty days therefore 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, like days is 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).

ParametersJSON Schema
NameRequiredDescriptionDefault
yearNoOne 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`.
replyNoOne 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.
titleNoHeading for the picker card shown to the user. At most 120 characters.
monthsNoHow 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.
purposeNoWhy 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.
subtitleNoSupporting line under the picker card's heading. At most 240 characters.
workspace_idNoTarget 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_idNoThe 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

ParametersJSON Schema
NameRequiredDescription
errorNo
hintsNo
periodsYes
successYes
next_stepNoWhat to do with the card this result renders. Added by the dispatcher when the card asks for a click.
workspace_idYes
base_currencyYes
default_periodYes
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
navigable_yearsYesThe calendar years a `year` request is answered for. A picker's year steppers stop here.
resolved_workspaceNoThe workspace that answered, when the caller named none and the token authorizes several.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere 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_monthYes

TDQS

A4.8/5.0
Behavior5/5

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.

Conciseness3/5

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.

Completeness5/5

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.

Parameters5/5

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

Schema coverage is 100%, so the baseline is 3, but the description 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.

Purpose5/5

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

The description states a specific verb and resource: '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.

Usage Guidelines5/5

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

The description gives explicit when-to-use guidance: 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 recurringA
Read-only
Inspect

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
toYesEXCLUSIVE end of the window: the first day of the month after the last one you want, YYYY-MM-01.
fromYesInclusive start of the window: the first day of a month, YYYY-MM-01.
workspace_idNoTarget 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_idNoThe 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

ParametersJSON Schema
NameRequiredDescription
errorNo
groupsYes
totalsYes
windowYes
partialYesTrue when the aggregate behind these figures was cut short. Every amount is then a FLOOR rather than a measurement.
successYes
next_stepNoWhat to do with the card this result renders. Added by the dispatcher when the card asks for a click.
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
unreadable_rowsYesInvoices 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_workspaceNoThe workspace that answered, when the caller named none and the token authorizes several.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.6/5.0
Behavior5/5

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.

Conciseness4/5

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.

Completeness5/5

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.

Parameters3/5

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

Schema description coverage is 100%, so the baseline is 3. The description 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.

Purpose5/5

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

The description opens with a specific verb+resource: '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.

Usage Guidelines5/5

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

The description gives explicit when-to-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 connectorsA
Read-only
Inspect

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
workspace_idNoTarget 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_idNoThe 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

ParametersJSON Schema
NameRequiredDescription
errorNo
successYes
next_stepNoWhat to do with the card this result renders. Added by the dispatcher when the card asks for a click.
candidatesNo
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
resolved_workspaceNoThe workspace that answered, when the caller named none and the token authorizes several.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.4/5.0
Behavior4/5

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.

Conciseness4/5

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.

Completeness5/5

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.

Parameters3/5

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.

Purpose5/5

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

The description states a specific verb and resource: 'Read the 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.

Usage Guidelines5/5

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

The description gives explicit when-to-use guidance: '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 windowA
Read-only
Inspect

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
toYesEXCLUSIVE end of the window, YYYY-MM-DD.
fromYesInclusive start of the window, YYYY-MM-DD.
limitNoMax rows to return (default 500).
workspace_idNoTarget 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_idNoThe 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

ParametersJSON Schema
NameRequiredDescription
errorNo
windowYes
recordsYes
successYes
returnedNo
truncatedNo
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
resolved_workspaceNoThe workspace that answered, when the caller named none and the token authorizes several.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.8/5.0
Behavior5/5

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.

Conciseness4/5

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.

Completeness5/5

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.

Parameters4/5

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

Schema coverage is 100%, so the baseline is 3. The description adds 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.

Purpose5/5

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.

Usage Guidelines5/5

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 postA
Read-only
Inspect

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoMax rows to return.
fiscal_yearYesThe fiscal year of the period to read.
workspace_idNoTarget 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_periodYesThe fiscal period, 1-12 for a month; 13 is the adjustment period and resolves to no rows.
conversation_idNoThe 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

ParametersJSON Schema
NameRequiredDescription
errorNo
recordsYes
successYes
returnedNo
scan_truncatedYes
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
resolved_workspaceNoThe workspace that answered, when the caller named none and the token authorizes several.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
in_flight_processingYes
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.8/5.0
Behavior5/5

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.

Conciseness4/5

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.

Completeness5/5

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

Given the tool's complexity, 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.

Parameters4/5

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

Schema coverage is 100%, so the baseline is 3, but the description adds meaningful 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.

Purpose5/5

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.

Usage Guidelines5/5

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 postA
Read-only
Inspect

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoMax rows to return.
fiscal_yearYesThe fiscal year of the period to read.
workspace_idNoTarget 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_periodYesThe fiscal period, 1-12 for a month; 13 is the adjustment period and resolves to no rows.
conversation_idNoThe 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

ParametersJSON Schema
NameRequiredDescription
errorNo
recordsYes
successYes
returnedNo
truncatedNo
ledger_catalogNo
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
resolved_workspaceNoThe workspace that answered, when the caller named none and the token authorizes several.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.9/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters4/5

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

Schema coverage is 100%, so the baseline is 3 and the 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.

Purpose5/5

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.

Usage Guidelines5/5

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

It gives explicit when-to-use 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 workspacesA
Read-only
Inspect

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 true shows that a bank has fed this workspace: false means no such transaction was found and null means 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.

ParametersJSON Schema
NameRequiredDescriptionDefault
workspace_idNoTarget workspace. Optional: this tool describes the token itself rather than one workspace's data, so omitting it returns the same answer.
conversation_idNoThe 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

ParametersJSON Schema
NameRequiredDescription
errorNo
sessionNoWhat this conversation's card clicks recorded so far; null/empty fields when nothing was clicked yet.
successYes
workspacesYes
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.8/5.0
Behavior5/5

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.

Conciseness4/5

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.

Completeness5/5

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

Given the tool's complexity, 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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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

The description gives explicit when-to-use guidance: 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 fetchA
Read-only
Inspect

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).

ParametersJSON Schema
NameRequiredDescriptionDefault
periodsNoSeveral calendar months in one call, 1-12. Each month costs one separate read, so name only the months you need. Duplicates are refused.
fiscal_yearNoFiscal year (the calendar year the workspace's fiscal year STARTED in).
workspace_idNoTarget 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_yearNoCalendar year, e.g. 2026.
fiscal_periodNoFiscal period, 1-12. The adjustment period (13) is refused: it has no calendar month.
calendar_monthNoCalendar month, 1 = January … 12 = December.
conversation_idNoThe 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

ParametersJSON Schema
NameRequiredDescription
modeNo
errorNo
hintsNo
countsNoThe 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.
monthsNoPer-month route counts, oldest first.
successYes
vendorsYesEVERY 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_stepNoWhat to do with the card this result renders. Added by the dispatcher when the card asks for a click.
collect_urlNoThe `/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_yearNoPresent only when the call named exactly one month.
upload_rowsYesCounterparties whose invoice only a manual upload can obtain.
connect_rowsYesCounterparties whose invoice arrives by connecting a service.
period_labelNoHuman-readable label of the period, e.g. "June 2026". Present only when the call named one month.
workspace_idNo
base_currencyNo
calendar_yearNoPresent only when the call named exactly one month.
fiscal_periodNoPresent only when the call named exactly one month.
calendar_monthNoPresent only when the call named exactly one month.
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
periods_coveredNoThe months the result covers, oldest first.
selection_scopeNoWhat the pick removed. Present with `scoped_to_selected_counterparties`, so the size of the truncation is readable beside the result.
nothing_launchedNoAlways true — this tool never starts anything.
collect_url_omitsNoThe 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_requestedNoHow many calendar months the call named.
resolved_workspaceNoThe workspace that answered, when the caller named none and the token authorizes several.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.
collect_url_unaddressableNoThe 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_ownershipNoCounterparty 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_counterpartiesNoPresent 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

A4.5/5.0
Behavior5/5

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.

Conciseness2/5

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.

Completeness5/5

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.

Parameters5/5

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

Schema coverage is 100%, so the baseline is 3, but the description adds 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.

Purpose5/5

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

The description opens with a specific verb and resource: '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.

Usage Guidelines4/5

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

The description states when to use 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 stepsA
Read-only
Inspect

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:

  • skill the 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.

  • prompt one 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 place

  • a 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.

ParametersJSON Schema
NameRequiredDescriptionDefault
stepsYesExactly 5 steps, in the order the card lists them.
workspace_idNoTarget workspace. Optional: this tool describes the token itself rather than one workspace's data, so omitting it returns the same answer.
conversation_idNoThe 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

ParametersJSON Schema
NameRequiredDescription
hintYes
errorNo
stepsYes
reasonYes
successYes
next_stepNoWhat to do with the card this result renders. Added by the dispatcher when the card asks for a click.
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.9/5.0
Behavior5/5

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.

Conciseness4/5

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

The description is long but front-loaded with the core purpose and 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.

Completeness5/5

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

Given the tool's complexity, 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.

Parameters5/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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 recordsA
Read-only
Inspect

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:

  1. Call well_get_schema(root) FIRST to discover the available fields.

  2. Name in fields ONLY the extra values you need (5-15 typically). They are ADDED to the root's default projection in the payload you read.

  3. Filter with whereClause so 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 ids well_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 partyScope instead: 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 no partyScope and 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:

  • fields is ADDITIVE — it widens the data you receive on top of the root's default projection

  • Omitting 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: read totalCount, 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 nextCursor is NOT a to-do. It means more rows exist, which totalCount already 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 nextCursor as cursor; nextCursor: null is the last page.

Returns { rows, totalCount, nextCursor, success }.

ParametersJSON Schema
NameRequiredDescriptionDefault
rootYesThe 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.
limitNoMax records to return (default 50, max 500)
cursorNoOpaque cursor for the next page. Omit for the first page, then pass nextCursor from the previous response.
fieldsNoEXTRA 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.
orderByNoSort results by a field. Example: { field: "grand_total", direction: "desc" }
allFieldsNoIf true, automatically fetches all scalar fields from schema. No need to specify fields.
partyScopeNoWhich 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.
whereClauseNoHasura-style filter object. Operators: _eq, _neq, _gt, _gte, _lt, _lte, _like, _ilike, _in, _nin, _is_null. Example: { "status": { "_eq": "unpaid" } }
workspace_idNoTarget workspace. Omit to query every authorized workspace at once; each row comes back tagged with the workspace it belongs to.
conversation_idNoThe 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

ParametersJSON Schema
NameRequiredDescription
rowsYesQuery results
errorNo
columnsNoThe 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.
successYes
returnedYesNumber of rows returned
columnMetaNoPer-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.
nextCursorNoCursor for the next page. null means last page.
totalCountYesTotal matching records
records_urlNoLogin-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_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.9/5.0
Behavior5/5

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.

Conciseness4/5

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.

Completeness5/5

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.

Parameters5/5

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

Schema coverage is 100%, so the baseline is 3, but the description adds 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.

Purpose5/5

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.

Usage Guidelines5/5

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 channelA
Destructive
Inspect

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
parentYesParent record type: company or person
channelYesChannel to remove: email | phone | web_link | location
parent_idYesUUID of the parent company or person
channel_idYesUUID of the specific channel row to remove
workspace_idNoTarget 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_idNoThe 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_keyNoOptional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation.

Output Schema

ParametersJSON Schema
NameRequiredDescription
errorNo
parentNo
channelNo
successYes
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.4/5.0
Behavior4/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters4/5

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

Schema coverage is 100%, so the baseline is 3. The description adds 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.

Purpose5/5

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.

Usage Guidelines4/5

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 burnA
Read-only
Inspect

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 in currency

  • window — the months the average divides by, not the months that carried spend

  • convention — "signed", and the counts you elected it from

  • months_in_window and months_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 window

  • excluded — what fell out, in named groups. internal_transfers is the sum's excluded_multi_leg; send null when the sum could not count it

  • transaction_count and unplaceable_count — how much of the window could be placed inside or outside the transfer rule at all. unplaceable_count is the sum's excluded_no_owned_leg. Send null when 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 magnitude

  • convention: "magnitude" — that feed keeps direction in a field no grouping here reaches, so no outflow was measured

  • months_with_data above months_in_window, or a measured unplaceable_count above transaction_count

  • one of unplaceable_count and excluded.internal_transfers null without the other: one cancelled count nulls both

  • a months_in_window that disagrees with the months window spans — the two state one fact, and a reader cannot tell which is the lie

  • signed elected from ZERO negative rows: whatever the convention was called, that window measured no outflow

  • convention_counts summing past transaction_count, or months_with_data disagreeing with the months per_month shows carrying an outflow — your own prose states both, so a contradiction between them is a sentence that refutes itself

  • a window whose bounds are not each the first of a month, or that fits inside one month: a month average divides by whole months

  • a per_month series that is not the window's own months, in order, averaging to amount — a dark month belongs in it as a zero, and a series that disagrees with the figure is not the working behind it

  • a currency outside ISO-4217 — the code is checked against the catalog, not its shape

OPTIONAL, and only as a pair:

  • baseline and change — 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. change is checked against amount and baseline.value and 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 from change rather 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.

ParametersJSON Schema
NameRequiredDescriptionDefault
amountYesAverage monthly outflow as a POSITIVE magnitude. A negative value is refused.
changeNoSigned 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.
windowYesInclusive start and EXCLUSIVE end of the averaged window, YYYY-MM-DD.
baselineNoThe 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.
currencyYesISO-4217 code the amount is denominated in. Checked against the catalog, not its shape.
excludedYesThe three exclusion groups kept apart: structural, reader-chosen, and defective.
per_monthNoThe series behind the average. A month with no outflow belongs in it as a zero.
conventionYesWhich sign the feed uses for an outflow. "magnitude" is refused: it measures no outflow.
workspace_idNoTarget 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_idNoThe 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_windowYesThe divisor — every month in the window.
months_with_dataYesHow many of those months carried any outflow.
convention_countsYesThe row counts the convention was elected from, so a reader can check the election.
transaction_countYesRows in the window.
unplaceable_countYesRows 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

ParametersJSON Schema
NameRequiredDescription
trendNo
amountYes
changeNo
windowYes
successYes
baselineNo
currencyYes
excludedYes
per_monthNo
conventionYes
computed_byYes
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
months_in_windowYes
months_with_dataYes
convention_countsYes
transaction_countYes
unplaceable_countYes
resolved_workspaceNoThe workspace that answered, when the caller named none and the token authorizes several.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.8/5.0
Behavior5/5

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.

Conciseness4/5

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.

Completeness5/5

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.

Parameters4/5

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

Schema coverage is 100%, so the baseline is 3. The description adds 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.

Purpose5/5

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

The description opens with a specific verb-resource pair ('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.

Usage Guidelines5/5

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 bridgeA
Read-only
Inspect

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 it

  • period_start, period_end — the inclusive calendar days the flows cover

  • opening — amount (SIGNED, a workspace can be overdrawn), as_of (the day before period_start), and derived (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 are

  • unexplained — the SIGNED gap closing - (opening + inflows - outflows), computed from the figures as you rounded them; zero when they meet

  • closing — amount (SIGNED) and as_of, the moment the reading was taken

  • reconciles — true when the gap is inside the tolerance below, false when it is past it

  • partial — 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: true with a gap past the tolerance, or false with one inside it

  • a negative inflows or outflows; each is a magnitude, so a negative one was re-signed

  • an opening not dated the day before period_start

  • a 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_of in 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.

ParametersJSON Schema
NameRequiredDescriptionDefault
closingYes
inflowsYes
openingYes
partialYes
currencyYes
outflowsYes
period_endYes
reconcilesYes
unexplainedYes
period_startYes
workspace_idNoTarget 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_idNoThe 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

ParametersJSON Schema
NameRequiredDescription
stepsYes
closingYes
inflowsYes
openingYes
partialYes
successYes
currencyYes
outflowsYes
period_endYes
reconcilesYes
computed_byYes
unexplainedYes
period_startYes
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
resolved_workspaceNoThe workspace that answered, when the caller named none and the token authorizes several.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A5/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters5/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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 forecastA
Read-only
Inspect

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, and as_of: the full ISO time of the balances read the series came from

  • actuals: one { month, amount } per month, oldest first, ending on the last month that has ended at as_of (UTC). A month no account covered is null, never 0, and it stays in the list.

  • anchor: { month, amount, basis }. closed_month_end is the latest settled month-end in actuals. current_position is 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 the window it averaged (from inclusive and to exclusive, each YYYY-MM-01). The window ends with the last actual month.

  • months_forward (at most 12), and projection: one { month, amount } per projected month. When the anchor sits before the last actual month, the months between are projected too, so months_forward must 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 under current_position), and the four exclusion groups

  • burn_policy: the elected convention and its counts, the exclusions (internal_transfers is the sum's excluded_multi_leg, unreadable_rows its malformed rows), and unplaceable_count (the sum's excluded_no_owned_leg)

  • partial: the forecast's own floor, which is WIDER than a cash total's is_floor. It is checked against cash_scope and must be true exactly 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's is_floor unchanged 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 cent

  • a 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", signed elected from no negative rows, or one of unplaceable_count and internal_transfers null without the other

  • a closed_month_end anchor that is not the latest settled actual, or whose amount differs from it

  • a current_position anchor beside a settled actual, off the last actual month, or with an account missing at it

  • actual months out of order, repeated, skipped, or ending on any month but the last one that has ended at as_of

  • a burn window that disagrees with trailing_months, or ends on a different month than the actuals

  • a partial that disagrees with the floor your own cash_scope implies

  • an as_of in 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.

ParametersJSON Schema
NameRequiredDescriptionDefault
burnYes
as_ofYes
anchorYes
actualsYes
partialYes
currencyYes
cash_scopeYes
projectionYes
burn_policyYes
workspace_idNoTarget 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_forwardYes
conversation_idNoThe 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

ParametersJSON Schema
NameRequiredDescription
burnYes
as_ofYes
anchorYes
entriesYes
partialYes
successYes
currencyYes
cash_scopeYes
burn_policyYes
computed_byYes
months_forwardYes
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
resolved_workspaceNoThe workspace that answered, when the caller named none and the token authorizes several.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.9/5.0
Behavior5/5

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.

Conciseness4/5

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.

Completeness5/5

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.

Parameters5/5

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.

Purpose5/5

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

The description opens with a specific verb+resource: '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.

Usage Guidelines5/5

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

The description explicitly states when to 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 positionA
Read-only
Inspect

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:

  • amount and currency — 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 for

  • accounts — every account that CONTRIBUTED, each with its native amount and currency, the converted amount, and the rate applied (null when it was already in currency). Carry institution_name and masked_account_number through 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 both null, and never by its id.

  • scope — the account types you counted as cash, and whether you counted an account whose ownership is unsettled

  • excluded — 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 from excluded, 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 currency that carries a rate other than one, or whose converted amount differs from its native one

  • an account in another currency that states no rate

  • the same account contributing twice

  • a non-zero total with no contributing accounts

  • an as_of in the future

  • a scope.account_types naming nothing

  • a balance_history that repeats a month or runs out of order

  • a currency outside ISO-4217 — checked against the catalog, not its shape

OPTIONAL:

  • balance_history — trailing complete month ends, oldest first. A null amount 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.

ParametersJSON Schema
NameRequiredDescriptionDefault
as_ofYes
scopeYes
amountYes
partialYes
accountsYes
currencyYes
excludedYes
workspace_idNoTarget 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_historyNo
conversation_idNoThe 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

ParametersJSON Schema
NameRequiredDescription
as_ofYes
scopeYes
amountYes
partialYes
successYes
accountsYes
currencyYes
excludedYes
computed_byYes
balance_historyNo
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
resolved_workspaceNoThe workspace that answered, when the caller named none and the token authorizes several.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.7/5.0
Behavior5/5

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.

Conciseness4/5

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.

Completeness5/5

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

Given the tool's complexity (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.

Parameters5/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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

The description explicitly states when to call ('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 structureA
Read-only
Inspect

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 in currency. Send NO share: this tool derives every share from the amounts and returns them, and an entry carrying pct is refused as an unknown field. At most 4 named slices plus one rolled-up Other, because the card performs no rollup of its own

  • period_start and period_end: the INCLUSIVE bounds of the single calendar month covered. Never a quarter, never a span, never a month still running

  • rung: 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's

  • label_provenance: whether a person owns those labels. A chart of accounts synced from an accounting tool is machine, not curated: the names came from the provider, not from anyone at the company

  • coverage: 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 it

  • convention and convention_counts: which sign means money leaving, and the row counts you elected it from

  • excluded: what fell out, in four named groups. no_asset_movement is where CARD SPEND lands, because the transfer rule drops a row with no owned asset leg and a card charge moves a liability. It contains no_owned_leg, so never add them. Send an unmeasured LEG count as null rather than 0, because zero says the rule removed nothing, and one cancelled leg count nulls all three. unreadable_rows is 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 reconcile

  • entries out of descending-amount order, more than 4 named slices, or an Other slice that is not last

  • a negative amount: a breakdown is made of magnitudes

  • a period_start/period_end pair that is not exactly one whole calendar month, or that names a month which has not ended

  • category_key on any rung but category_key, or on the rolled-up Other slice, which is many categories and is therefore not one of them

  • any label_provenance but unlabelled on a rung that carries no category: curated, machine and mixed each 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 legitimately unlabelled

  • rung: "uncategorised" sent beside named category slices, which is a breakdown claiming to be the absence of one

  • convention: "magnitude": that feed keeps direction in a field no grouping reaches, so no outflow was measured. signed elected from ZERO negative rows is the same finding, demonstrated rather than declared

  • coverage 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_leg and excluded.no_asset_movement null while the others are measured: one cancelled count nulls all three, and the refusal is filed against excluded.no_asset_movement

  • a currency outside 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.

ParametersJSON Schema
NameRequiredDescriptionDefault
rungYesWhich grouping produced these categories.
entriesYesThe slices, largest first, with the rolled-up `Other` last when there is one.
coverageYesThe evidence the rung was elected on, so a reader can check the election rather than take it.
currencyYesISO-4217 code every amount is denominated in. Checked against the catalog, not its shape.
excludedYesThe 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.
conventionYesWhich sign the feed uses for an outflow. "magnitude" is refused: it measures no outflow.
period_endYesINCLUSIVE last day of that same month.
period_startYesINCLUSIVE first day of the month covered.
workspace_idNoTarget 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_idNoThe 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_provenanceYesWhether a person set or confirmed the labels the reader can see.
convention_countsYesThe row counts the convention was elected from.

Output Schema

ParametersJSON Schema
NameRequiredDescription
rungYes
entriesYes
successYes
coverageYes
currencyYes
excludedYes
conventionYes
period_endYes
computed_byYes
period_startYes
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
label_provenanceYes
convention_countsYes
resolved_workspaceNoThe workspace that answered, when the caller named none and the token authorizes several.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A5/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters5/5

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.

Purpose5/5

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

The description opens with a specific verb and resource: '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.

Usage Guidelines5/5

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

The description explicitly says when to 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 figureA
Read-only
Inspect

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 in currency, net of tax and of credit notes, as the sum returned it

  • window — the months the average divides by, not the months that carried revenue

  • months_in_window and months_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 window

  • recurring_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 whose billing_context is null) 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 unchecked

  • invoice_count, unattributed_count and unclassified_count — how much of the window the figure could reach at all. invoice_count is the issued invoices; unattributed_count is 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 when recurring_contexts holds "unclassified", and when it is, say in prose that the reader chose to count it. Send null for a count the sum returned as null: it is unmeasured, not zero

  • excluded — what fell out, as three invoice COUNTS kept apart: one_off (issued invoices under a context the reader did not count), credit_notes (the credit_note_count netted into the figure) and unreadable_rows (the sum's excluded_malformed, null when 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 magnitude

  • an 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 instead

  • months_with_revenue above months_in_window, or unclassified_count above invoice_count

  • a months_in_window that disagrees with the months window spans — the two state one fact

  • a window whose bounds are not month starts — a month-average divides by whole months

  • change with no baseline, a baseline.value of zero, a baseline that does not start before the window or spans a different number of months, or a change whose magnitude or sign its own two figures contradict

  • a per_month series that does not name each month of the window once in order, does not average to amount, or disagrees with months_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.

ParametersJSON Schema
NameRequiredDescriptionDefault
amountYesRecurring revenue per month, net of tax and credit notes. A negative net is refused, never flipped.
changeNoSigned percentage against `baseline.value`, drawn as the card's trend chip. Send it only alongside `baseline`.
windowYesInclusive start and EXCLUSIVE end of the averaged window, YYYY-MM-DD.
baselineNoThe earlier window this figure is compared against. Required for `change` to render, and never rendered itself: name it in prose.
currencyYesISO-4217 code the amount is denominated in.
excludedYesThe three exclusion groups kept apart: the reader's choice, the structural netting, and the defective rows.
per_monthNoThe series behind the average. A month with no recurring revenue belongs in it as a zero.
workspace_idNoTarget 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_countYesIssued invoices in the window.
conversation_idNoThe 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_windowYesThe divisor — every month in the window.
recurring_contextsYesThe 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_countYesInvoices Well could place on neither side: a set separate from `invoice_count`, which may be larger. Null when unmeasured.
unclassified_countYesInvoices carrying no billing context. In the figure only when `recurring_contexts` holds "unclassified".
months_with_revenueYesHow many of those months carried any recurring revenue.

Output Schema

ParametersJSON Schema
NameRequiredDescription
trendNo
amountYes
changeNo
windowYes
successYes
baselineNo
currencyYes
excludedYes
per_monthNo
computed_byYes
invoice_countYes
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
months_in_windowYes
recurring_contextsYes
resolved_workspaceNoThe workspace that answered, when the caller named none and the token authorizes several.
unattributed_countYes
unclassified_countYes
months_with_revenueYes
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.9/5.0
Behavior5/5

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.

Conciseness4/5

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.

Completeness5/5

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.

Parameters5/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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 runwayA
Read-only
Inspect

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 36

  • status — "ok", "capped", "infinite" or "insufficient_data"

  • cash — the dividend, amount and currency. SIGNED: an overdrawn workspace is negative. null ONLY 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 the trailing_months it averaged

  • as_of — the moment the reading is valid for

  • partial — 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 when avg_burn is null.

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:

  • months that is not cash ÷ avg_burn — the division is the figure's whole claim

  • a 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_of in the future

  • a window whose bounds are not each the first of a month, or that spans no month

  • a window that spans a different number of months than avg_burn.trailing_months — the two state one fact, and a reader cannot tell which is the lie

  • a window sent with no burn to average — an unmeasured burn averaged no months

  • a window reaching into a month that has not ended yet

  • a window bound 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.

ParametersJSON Schema
NameRequiredDescriptionDefault
cashYes
as_ofYes
monthsYes
statusYes
windowNoInclusive start and EXCLUSIVE end of the months the burn averaged, YYYY-MM-DD.
partialYes
avg_burnYes
workspace_idNoTarget 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_idNoThe 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

ParametersJSON Schema
NameRequiredDescription
cashYes
as_ofYes
monthsYes
statusYes
windowNo
partialYes
successYes
avg_burnYes
computed_byYes
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
resolved_workspaceNoThe workspace that answered, when the caller named none and the token authorizes several.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.6/5.0
Behavior5/5

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.

Conciseness3/5

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.

Completeness5/5

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.

Parameters5/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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 postingA
Idempotent
Inspect

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
workspace_idNoTarget 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_idNoThe 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_keyNoOptional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation.

Output Schema

ParametersJSON Schema
NameRequiredDescription
errorNo
skippedYes
successYes
enqueuedYes
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.7/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters3/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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

The description gives explicit when-to-use ('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.

ParametersJSON Schema
NameRequiredDescriptionDefault
actionYes
task_idsYesThe review tasks' task_id values.
selectionNoFor 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_idNoTarget 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_idNoThe 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_keyNoOptional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation.

Output Schema

ParametersJSON Schema
NameRequiredDescription
errorNo
errorsNo
successYes
resolvedNo
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.7/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness5/5

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

Given the tool's complexity (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.

Parameters4/5

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.

Purpose5/5

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

The description opens with a specific verb phrase — '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.

Usage Guidelines4/5

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
task_idYesThe gap review task's task_id.
workspace_idNoTarget 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_idNoThe 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_keyNoOptional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation.
bank_ledger_account_idYesledger_account_id of the bank/cash account.
category_ledger_account_idYesledger_account_id of the category account.

Output Schema

ParametersJSON Schema
NameRequiredDescription
errorNo
successYes
task_idNo
external_idNo
already_postedNo
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.6/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters4/5

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

Schema coverage is 100%, so the baseline is 3. The description adds value by 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.

Purpose5/5

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

The description states a specific action and resource: '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.

Usage Guidelines4/5

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 connectorsA
Idempotent
Inspect

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
workspace_idNoTarget 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_idNoThe 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_keyNoOptional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation.
source_workspace_connector_idsYesThe workspace_connector_id of each parent connector to bring across, copied from well_list_retargetable_connectors or well_show_retargetable_connectors.

Output Schema

ParametersJSON Schema
NameRequiredDescription
errorNo
successYes
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere 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_idsNoThe connector rows created on this workspace, one per newly retargeted source.
already_retargeted_workspace_connector_idsNoSource ids that had already been retargeted — an expected replay, not an error.

TDQS

A4.7/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters4/5

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

Schema coverage is 100%, so the baseline is 3, but the description adds meaning beyond the schema 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.

Purpose5/5

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.

Usage Guidelines4/5

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
since_dateNoOnly diff bank transactions on/after this date (YYYY-MM-DD).
workspace_idNoTarget 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_idNoThe 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_keyNoOptional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation.
bank_workspace_connector_idYesThe bank connector's workspace_connector_id (e.g. Plaid).
register_workspace_connector_idYesThe accounting connector's workspace_connector_id (e.g. QuickBooks).

Output Schema

ParametersJSON Schema
NameRequiredDescription
errorNo
enabledNo
matchedNo
successYes
gaps_proposedNo
already_linkedNo
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
raised_for_reviewNo
missing_in_registerNo
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
contended_in_registerNo
gaps_already_proposedNo
conversation_id_sourceNoWhere 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_idsNo
unexplained_in_registerNo
contended_in_register_idsNo
unexplained_in_register_idsNo

TDQS

A4.4/5.0
Behavior5/5

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.

Conciseness4/5

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.

Completeness5/5

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

Given the tool's complexity, 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.

Parameters3/5

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.

Purpose5/5

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

The description opens with a specific verb and resource: 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.

Usage Guidelines4/5

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 registryA
Read-only
Inspect

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
qYesThe company name to search for. At least 2 characters.
countryNoScope the search to this jurisdiction. Omit to search across registries.
workspace_idNoTarget workspace. Optional: this tool describes the token itself rather than one workspace's data, so omitting it returns the same answer.
conversation_idNoThe 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

ParametersJSON Schema
NameRequiredDescription
hitsYes
errorNo
successYes
degradedYesTrue when a registry provider was unreachable and the hits are partial; the search still returned what it could.
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.7/5.0
Behavior4/5

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

Annotations already declare readOnlyHint=true and 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.

Conciseness5/5

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.

Completeness5/5

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

For a read-only search tool with 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.

Parameters4/5

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

Schema description coverage is 100%, so the schema already documents all 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.

Purpose5/5

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.

Usage Guidelines5/5

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

The description explicitly says when to use 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 contextA
Read-only
Inspect

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
topKNo
queryYesFocused search query over the workspace's recorded notes and context.
categoryNo
entityIdNo
entityTypeNo
workspace_idNoTarget 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.
occurredAfterNo
occurredBeforeNo
conversation_idNoThe 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

ParametersJSON Schema
NameRequiredDescription
errorNo
resultsYes
successYes
total_returnedYes
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
resolved_workspaceNoThe workspace that answered, when the caller named none and the token authorizes several.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.9/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters4/5

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.

Purpose5/5

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

The description opens with a specific verb and resource: '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.

Usage Guidelines5/5

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 skillA
Read-only
Inspect

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
queryNoWhat the user wants to do, in their own terms. The whole roster is returned either way.
workspace_idNoTarget workspace. Optional: this tool describes the token itself rather than one workspace's data, so omitting it returns the same answer.
conversation_idNoThe 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

ParametersJSON Schema
NameRequiredDescription
errorNo
reasonYes
skillsYes
successYes
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.6/5.0
Behavior4/5

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.

Conciseness4/5

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.

Completeness5/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
categoryYesThe category label spend at this counterparty defaults to. Must be one of the labels in this list.
company_idYesThe counterparty to declare a default for, from well_list_counterparties.
workspace_idNoTarget 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_idNoThe 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_keyNoOptional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation.

Output Schema

ParametersJSON Schema
NameRequiredDescription
errorNo
successYes
categoryNoThe label the counterparty's default now carries.
company_idNo
company_nameNo
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.9/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness5/5

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

For a mutating tool with 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.

Parameters4/5

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.

Purpose5/5

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

The description opens with a specific verb and resource: '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.

Usage Guidelines5/5

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

It gives explicit when-to-use 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 companyA
Destructive
Inspect

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
company_idYesThe UUID of a company already in this workspace to anchor as its own company.
workspace_idNoTarget 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_idNoThe 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_keyNoOptional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation.

Output Schema

ParametersJSON Schema
NameRequiredDescription
errorNo
successYes
own_company_idNo
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.9/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters4/5

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

Schema coverage is 100%, so the baseline is 3. The description adds meaning beyond the schema 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.

Purpose5/5

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

The description states a specific verb and resource: 'Set 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.

Usage Guidelines5/5

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 keeps category_source: "classifier" and its confidence score, and the affirmation is stamped as category_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 records category_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.

ParametersJSON Schema
NameRequiredDescriptionDefault
categoryYesThe category label to store. Must be one of the labels in this list.
decisionNoHow the user arrived at the label. Omit for a user choice. See the description before sending accepted_classifier_suggestion.
workspace_idNoTarget 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_idYesThe transaction to categorize, from well_list_uncategorized_window.
conversation_idNoThe 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_keyNoOptional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation.

Output Schema

ParametersJSON Schema
NameRequiredDescription
errorNo
successYes
categoryNoThe label stored on the row after the write.
transaction_idNo
category_sourceNoProvenance the server settled on: "classifier" when it accepted the affirmation claim, "user" when the caller picked the label or the claim was downgraded.
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
category_confidenceNoThe classifier's score, preserved only on an accepted affirmation. Null on a user choice.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.7/5.0
Behavior5/5

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.

Conciseness4/5

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.

Completeness5/5

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

Given the tool's complexity 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.

Parameters5/5

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

Schema coverage is 100%, but the description adds 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.

Purpose5/5

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.

Usage Guidelines4/5

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
workspace_idNoTarget 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_idYesThe transaction to attach, from well_list_unposted_transactions.
conversation_idNoThe 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_keyNoOptional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation.
ledger_account_idYesThe ledger account to attach. `null` DETACHES the account currently on the row.
no_invoice_expectedNoAssert 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

ParametersJSON Schema
NameRequiredDescription
errorNo
postedNo
successYes
transaction_idNo
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
posting_outcomeNo
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A5/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters5/5

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

Although schema coverage is 100%, the description adds 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.

Purpose5/5

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

The description opens with a specific verb and resource: '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.

Usage Guidelines5/5

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

The description gives explicit when-to-use guidance: 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 settingsA
Read-only
Inspect

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
titleNoHeading 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.
requiredNoThe 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).
subtitleNoSupporting line under the card's heading. At most 240 characters. Omit for the default.
workspace_idNoTarget 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_idNoThe 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

ParametersJSON Schema
NameRequiredDescription
errorNo
successYes
requiredNo
settingsNo
next_stepNoWhat to do with the card this result renders. Added by the dispatcher when the card asks for a click.
suggestionsNo
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
resolved_workspaceNoThe workspace that answered, when the caller named none and the token authorizes several.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.6/5.0
Behavior5/5

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.

Conciseness4/5

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.

Completeness5/5

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.

Parameters3/5

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

Schema description coverage is 100%, so the schema already documents 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.

Purpose5/5

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

The description opens with a specific verb-resource pair ('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.

Usage Guidelines5/5

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 candidatesA
Read-only
Inspect

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
workspace_idNoTarget 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_idNoThe 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

ParametersJSON Schema
NameRequiredDescription
errorNo
anchorYes
successYes
next_stepNoWhat to do with the card this result renders. Added by the dispatcher when the card asks for a click.
candidatesYes
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
resolved_workspaceNoThe workspace that answered, when the caller named none and the token authorizes several.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.9/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters4/5

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

Schema coverage is 100%, so the baseline is 3. The description adds 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.

Purpose5/5

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.

Usage Guidelines5/5

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

The description provides explicit when-to-use ('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 recordsA
Read-only
Inspect

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 ids well_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 partyScope instead: 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 no partyScope and 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 fields to show the user a table — that is what renders the root's own columns

  • fields is ADDITIVE and for values YOU need to reason about: it widens the payload you read and never reorders or trims the columns the user sees

  • Field 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 nextCursor is NOT a to-do. It means more rows exist, which totalCount already 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 nextCursor as cursor; nextCursor: null is the last page.

Returns { rows, totalCount, nextCursor, success }.

ParametersJSON Schema
NameRequiredDescriptionDefault
rootYesThe 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.
limitNoMax records to return (default 50, max 500)
cursorNoOpaque cursor for the next page. Omit for the first page, then pass nextCursor from the previous response.
fieldsNoEXTRA 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.
orderByNoSort results by a field. Example: { field: "grand_total", direction: "desc" }
allFieldsNoIf true, automatically fetches all scalar fields from schema. No need to specify fields.
partyScopeNoWhich 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.
whereClauseNoHasura-style filter object. Operators: _eq, _neq, _gt, _gte, _lt, _lte, _like, _ilike, _in, _nin, _is_null. Example: { "status": { "_eq": "unpaid" } }
workspace_idNoTarget workspace. Omit to query every authorized workspace at once; each row comes back tagged with the workspace it belongs to.
conversation_idNoThe 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

ParametersJSON Schema
NameRequiredDescription
rowsYesQuery results
errorNo
columnsNoThe 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.
successYes
returnedYesNumber of rows returned
columnMetaNoPer-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.
nextCursorNoCursor for the next page. null means last page.
totalCountYesTotal matching records
records_urlNoLogin-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_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.9/5.0
Behavior5/5

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.

Conciseness4/5

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.

Completeness5/5

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.

Parameters5/5

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

Although schema coverage is 100%, the description adds 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.

Purpose5/5

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.

Usage Guidelines5/5

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

Provides explicit when-to-use guidance 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 connectorsA
Read-only
Inspect

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
titleNoHeading 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.
subtitleNoSupporting line under the card's heading. At most 240 characters. Omit for the default.
workspace_idNoTarget 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_idNoThe 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

ParametersJSON Schema
NameRequiredDescription
errorNo
successYes
next_stepNoWhat to do with the card this result renders. Added by the dispatcher when the card asks for a click.
candidatesNo
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
resolved_workspaceNoThe workspace that answered, when the caller named none and the token authorizes several.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.6/5.0
Behavior5/5

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

Beyond the readOnlyHint annotation, the description discloses 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.

Conciseness4/5

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.

Completeness5/5

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.

Parameters3/5

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.

Purpose5/5

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

The description states a specific verb and resource: '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.

Usage Guidelines5/5

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

The description is explicit about when to use 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 pickerA
Read-only
Inspect

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
replyNoOne 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.
titleNoHeading 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.
subtitleNoSupporting 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_idNoTarget workspace. Optional: this tool describes the token itself rather than one workspace's data, so omitting it returns the same answer.
workspace_idsNoScope 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_idNoThe 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

ParametersJSON Schema
NameRequiredDescription
errorNo
sessionNoWhat this conversation's card clicks recorded so far; null/empty fields when nothing was clicked yet.
successYes
next_stepNoWhat to do with the card this result renders. Added by the dispatcher when the card asks for a click.
workspacesYes
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.7/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters3/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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 windowA
Read-only
Inspect

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
toYesEXCLUSIVE end of the window: the first day of the month after the last one you want, YYYY-MM-01.
fromYesInclusive start of the window: the first day of a month, YYYY-MM-01.
party_scopeYesWhich side of the invoice the workspace occupies: `sales` for what it issued, `purchase` for what it received. Required; see the description.
workspace_idNoTarget 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_idNoThe 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

ParametersJSON Schema
NameRequiredDescription
rowsYes
errorNo
windowYes
partialYes
successYes
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
excluded_malformedYes
resolved_workspaceNoThe workspace that answered, when the caller named none and the token authorizes several.
unattributed_countYes
unclassified_countYes
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere 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_countYes

TDQS

A5/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters5/5

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

Schema coverage is 100%, so the baseline is 3, but the description 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.

Purpose5/5

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.

Usage Guidelines5/5

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

Gives explicit when-to-use guidance: '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 windowA
Read-only
Inspect

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 the ledger_accounts root for the connector that wrote it. A blank label reads as null. 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 value exempt_categories accepts. 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 to exempt_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.

ParametersJSON Schema
NameRequiredDescriptionDefault
toYesEXCLUSIVE end of the window, ISO-8601 — the first instant after the last month you want.
axesNoGroup the sums by these, IN ADDITION to currency. Omit for one row per currency over the whole window.
fromYesInclusive start of the window, ISO-8601 (e.g. 2026-06-01).
scopeYesWhich 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_idNoTarget 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_idNoThe 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_categoriesNoCategory keys that do not count. An uncategorized row is never matched by one.
exclude_internal_transfersNoKeeps 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

ParametersJSON Schema
NameRequiredDescription
rowsYes
errorNo
windowYes
partialYes
successYes
rows_truncatedYes
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
excluded_zero_legYes
excluded_malformedYes
excluded_multi_legYes
resolved_workspaceNoThe workspace that answered, when the caller named none and the token authorizes several.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
excluded_no_owned_legYes
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.9/5.0
Behavior5/5

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.

Conciseness4/5

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.

Completeness5/5

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.

Parameters5/5

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

Schema coverage is 100%, but the description adds substantial meaning beyond 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.

Purpose5/5

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.

Usage Guidelines5/5

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

The description explicitly states when to use it: 'Use it 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.

ParametersJSON Schema
NameRequiredDescriptionDefault
ackNoAcknowledge 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.
periodsNoThe 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_stepNoThe 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_outcomeNoWhat 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_idNoTarget 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_idsNoOrdered 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.
counterpartiesNoThe 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_idNoThe 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_keyNoOptional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation.
exempt_categoriesNoThe 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_contextsNoThe 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_periodsNoThe 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_typesNoThe 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

ParametersJSON Schema
NameRequiredDescription
errorNo
pickupYesWhat 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.
changedNoWhich fields of this conversation's context this call wrote.
resumedYesTrue 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.
successYes
warningNoPresent 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.
acknowledgedNo
workspace_idNo
workspace_nameNo
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
workspace_queueNoThe workspaces queued after the pinned one, in order.
selected_periodsNo
selected_next_stepNoThe next step this call recorded, echoed back as the card sent it.
selected_cash_scopeNo
selected_exemptionsNo
acknowledged_outcomeNoThe answer the acknowledging click carried, echoed back on a step whose card offers two.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.
selected_counterpartiesNo
selected_recurring_contextsNo

TDQS

A4.9/5.0
Behavior5/5

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

Annotations only indicate readOnlyHint=false and destructiveHint=false, 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.

Conciseness4/5

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.

Completeness5/5

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.

Parameters5/5

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

Schema coverage is 100%, so the baseline is 3, but the description 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.

Purpose5/5

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.

Usage Guidelines5/5

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

The description gives explicit when-to-use and when-not-to-use guidance: '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 companyA
Destructive
Inspect

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
nameNoCompany name
domainNoPrimary website domain (e.g. acme.com)
localeNoPreferred language as an ISO 639-1 two-letter code (e.g. en, fr, de). Pass null to clear.
decisionNoHow 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_idYesThe UUID of the company to update (required)
trade_nameNoTrading name / DBA
descriptionNoBrief company description; pass null to clear
tax_id_typeNoTax identifier type (VAT, SIRET, EIN, ...)
category_idsNoThe COMPLETE set of company-category ids this company should carry. Replaces the current set; [] clears it; omit to leave categories unchanged.
tax_id_valueNoTax identifier value (VAT, SIRET, EIN, ...)
workspace_idNoTarget 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_typeNoBusiness type / legal form
registry_nameNoRegistry name
conversation_idNoThe 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_keyNoOptional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation.
registered_nameNoOfficial registered legal name
registered_valueNoRegistry identifier value
registry_countryNoISO 3166-1 alpha-2 country code of the registry (e.g. FR, US)
ledger_default_sourceNoHow 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_idNoThe 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_idNoThe 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

ParametersJSON Schema
NameRequiredDescription
nameNo
errorNo
successYes
decisionNo
company_idNo
category_countNo
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.9/5.0
Behavior5/5

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.

Conciseness4/5

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.

Completeness5/5

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.

Parameters5/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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 invoiceA
Destructive
Inspect

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
termsNoPayment terms text; pass null to clear
statusNoInvoice lifecycle status
due_dateNoPayment due date (ISO 8601); pass null to clear
tax_totalNoTotal tax amount; pass null to clear
invoice_idYesThe UUID of the invoice to update
issue_dateNoIssue date (ISO 8601, e.g. 2026-04-27); pass null to clear
descriptionNoFree-form description; pass null to clear
grand_totalNoTotal invoice amount including tax; pass null to clear
items_totalNoSum of line items before tax; pass null to clear
workspace_idNoTarget 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_currencyNoISO 4217 three-letter currency code (e.g. EUR, USD); pass null to clear
payment_statusNoUser-driven payment_status override — requires override_version (CAS)
billing_contextNoBilling context / business model; pass null to clear
conversation_idNoThe 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_keyNoOptional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation.
override_versionNoRequired when payment_status is present — current override_version for CAS
reference_numberNoInvoice reference number (e.g. INV-2026-001); pass null to clear
issuer_company_idNoIssuer company UUID. Omit = no change, null = clear, uuid = set.
document_type_codeNoUN/CEFACT 1001 document type code (e.g. 380 for commercial invoice); pass null to clear
receiver_company_idNoReceiver company UUID. Omit = no change, null = clear, uuid = set.

Output Schema

ParametersJSON Schema
NameRequiredDescription
errorNo
successYes
invoice_idNo
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
reference_numberNo
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.4/5.0
Behavior4/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters4/5

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

Schema coverage is 100%, so the baseline is 3. The description adds value by 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.

Purpose5/5

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.

Usage Guidelines4/5

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 personA
Destructive
Inspect

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
job_titleNoJob title; pass null to clear
last_nameNoLast name
person_idYesThe UUID of the person to update (required)
first_nameNoFirst name
workspace_idNoTarget 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_idNoThe 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_keyNoOptional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation.

Output Schema

ParametersJSON Schema
NameRequiredDescription
errorNo
successYes
full_nameNo
person_idNo
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.8/5.0
Behavior5/5

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.

Conciseness4/5

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.

Completeness5/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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

The description provides explicit when-to-use guidance: '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.

ParametersJSON Schema
NameRequiredDescriptionDefault
filenameYesThe file's name WITH its extension, e.g. `invoice-2026-03.pdf`. The extension resolves the content type when `mime_type` is generic.
mime_typeYesThe file's content type, e.g. `application/pdf`. Send `application/octet-stream` when unknown and the extension decides.
workspace_idNoTarget 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_base64YesThe file's bytes, base64, with no data-URI prefix.
source_task_idNoThe task this document answers.
conversation_idNoThe 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_keyNoOptional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation.
source_transaction_idNoThe bank transaction this document is the proof for.

Output Schema

ParametersJSON Schema
NameRequiredDescription
errorNo
outcomeNo
successYes
filenameNo
max_bytesNo
mime_typeNo
error_codeNo
size_bytesNo
document_idNo
actual_bytesNo
deduplicatedNo
source_task_idNo
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
source_transaction_idNo
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A5/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness5/5

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

Given the tool's complexity (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.

Parameters5/5

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.

Purpose5/5

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

The description opens with a specific verb and object: '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.

Usage Guidelines5/5

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

The description explicitly tells the agent when 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).

ParametersJSON Schema
NameRequiredDescriptionDefault
sha256NoThe source file's SHA-256 (hex). When sent, a mismatch with the decoded bytes rejects the upload.
filenameYesThe statement's file name, e.g. "statement.csv". Only its extension selects the format.
workspace_idNoTarget 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_base64YesThe file's bytes, base64-encoded (RFC 4648; whitespace tolerated). Decoded cap: 5 MiB.
conversation_idNoThe 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_keyNoOptional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation.

Output Schema

ParametersJSON Schema
NameRequiredDescription
errorNo
successYes
byte_lengthNo
document_idNoPoll well_get_statement_import_result with this id for the import outcome.
deduplicatedNoTrue when an identical document was already in the workspace — nothing was imported twice.
content_sha256NoSHA-256 (hex) of the payload the server received — compare against your source to verify fidelity.
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.8/5.0
Behavior5/5

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

Annotations only declare readOnlyHint=false and destructiveHint=false; 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.

Conciseness4/5

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.

Completeness5/5

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.

Parameters4/5

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.

Purpose5/5

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

The description opens with a precise verb+resource 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.

Usage Guidelines5/5

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).

ParametersJSON Schema
NameRequiredDescriptionDefault
filenameYesThe statement's file name, e.g. "statement.csv". Only its extension selects the format.
content_textYesThe file's full text content, verbatim. UTF-8 encoded on the wire; capped at 1 MiB decoded.
workspace_idNoTarget 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_idNoThe 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_keyNoOptional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation.

Output Schema

ParametersJSON Schema
NameRequiredDescription
errorNo
successYes
byte_lengthNo
document_idNoPoll well_get_statement_import_result with this id for the import outcome.
deduplicatedNoTrue when an identical document was already in the workspace — nothing was imported twice.
content_sha256NoSHA-256 (hex) of the payload the server received — compare against your source to verify fidelity.
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.8/5.0
Behavior5/5

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.

Conciseness4/5

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.

Completeness5/5

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

Given the tool's complexity (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.

Parameters4/5

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

Schema coverage is 100%, so the baseline is 3. The description adds 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.

Purpose5/5

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.

Usage Guidelines5/5

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 settingsA
Destructive
Inspect

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
countryNoISO 3166-1 alpha-2 country code.
tax_id_typeNoThe tax id's type (SIREN, VAT, EIN, …). Provide it together with tax_id_value.
tax_id_valueNoThe company's tax id value. Provide it together with tax_id_type; one without the other is refused.
workspace_idNoTarget 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_currencyNoISO 4217 currency code.
coa_confirmedNoWhether the chart of accounts has been confirmed.
conversation_idNoThe 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_keyNoOptional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation.
incorporation_dateNoThe company's incorporation / registration date as YYYY-MM-DD, or null to clear it.
accounting_frameworkNoThe accounting framework the books follow.
fiscal_year_start_monthNoCalendar month (1-12) the fiscal year starts on, or null to clear it.
first_fiscal_year_start_dateNoFirst fiscal year start date as YYYY-MM-DD, or null to clear it.

Output Schema

ParametersJSON Schema
NameRequiredDescription
errorNo
countryNo
successYes
tax_id_typeNo
tax_id_valueNo
base_currencyNo
coa_confirmedNo
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
incorporation_dateNo
accounting_frameworkNo
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere 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_monthNo
first_fiscal_year_start_dateNo

TDQS

A4.9/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters4/5

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.

Purpose5/5

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

The description opens with a specific verb and resource: 'Set 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.

Usage Guidelines5/5

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 selectionA
Read-only
Inspect

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. selection carries 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 the periods they 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, the outcome the click carried. already_set: true means 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.

ParametersJSON Schema
NameRequiredDescriptionDefault
kindYesWhich 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_sNoHow long to wait, in seconds. Default 60, clamped to 5-60.
workspace_idNoTarget 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_noticeNoOne 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_idNoThe 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

ParametersJSON Schema
NameRequiredDescription
hintNo
kindNo
errorNo
statusNo
successYes
selectionNoThe value the click wrote. Present only when status is "selected".
already_setNoTrue when the click had already been recorded since the card was drawn, before this call started — no wait happened.
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
resolved_workspaceNoThe workspace that answered, when the caller named none and the token authorizes several.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

TDQS

A4.8/5.0
Behavior5/5

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.

Conciseness3/5

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.

Completeness5/5

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.

Parameters5/5

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

Although schema coverage is 100%, the description adds 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.

Purpose5/5

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

The description opens with a specific verb and object: '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.

Usage Guidelines5/5

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.

  1. 87 tool updates
    • Changedwell_add_contact_channel4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_assign_account4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_assign_missing_invoice_owners4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_claim_statement_draft4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_create_company4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_create_company_candidate4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_create_company_workspace4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_create_invoice_document4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_create_invoice_from_data4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_create_person4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_create_statement_upload4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_delete_company4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_delete_invoice4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_delete_person4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_enqueue_invoice_fetch4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_get_accounting_settings4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_get_connector_coverage7 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • changedInput schema / properties / from_selection / description
        Previous 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."
      • addedInput schema / properties / include_unsent_counts
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
      • addedOutput schema / properties / unsent_document_counts
        Added 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"
        +}
    • Changedwell_get_customer_einvoicing_details4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_get_design_tokens4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_get_entity4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Addedwell_get_graph
    • Changedwell_get_investment_holdings4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_get_own_company4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_get_schema4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_get_session_digest4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_get_skill5 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
      • changedOutput schema / properties / reason / anyOf
        Previous 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"
        +  }
        +]
    • Changedwell_get_statement_import_result4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_get_worklist_status4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_invite_members4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_invoke_connector_tool4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_list_account_balances5 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • changedInput schema / properties / months_back / description
        Previous 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."
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_list_accounts_needing_company4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_list_burn_exemptions4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_list_cash_scope4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_list_connector_tools4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_list_connectors7 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • changedInput schema / properties / from_selection / description
        Previous 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."
      • addedInput schema / properties / include_unsent_counts
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
      • addedOutput schema / properties / unsent_document_counts
        Added 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"
        +}
    • Changedwell_list_counterparties9 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedInput schema / properties / missing_ledger_only
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
      • addedOutput schema / properties / ledger_rows
        Added value: +{
        +  "items": {
        +    "additionalProperties": {},
        +    "propertyNames": {
        +      "type": "string"
        +    },
        +    "type": "object"
        +  },
        +  "type": "array"
        +}
      • changedOutput schema / properties / mode / description
        Previous 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`."
      • changedOutput schema / properties / mode / enum
        Previous value: -[
        -  "periods",
        -  "uncategorized_only"
        -]New value: +[
        +  "periods",
        +  "uncategorized_only",
        +  "missing_ledger_only"
        +]
      • changedOutput schema / properties / total_count / description
        Previous 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."
    • Addedwell_list_ledger_accounts
    • Changedwell_list_member_candidates4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_list_missing_invoice_owners4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_list_missing_invoices4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_list_periods4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_list_recurring_contexts4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_list_retargetable_connectors4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_list_uncategorized_window4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Addedwell_list_unposted_journals
    • Changedwell_list_unposted_transactions4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_list_workspaces5 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
      • changedOutput schema / properties / session / description
        Previous 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."
    • Changedwell_preview_invoice_fetch4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_propose_next_steps4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_query_records4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_remove_contact_channel4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_render_burn4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_render_cash_flow_bridge4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_render_cash_forecast4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_render_cash_position4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_render_cost_structure4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_render_mrr4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_render_runway4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Addedwell_repost_journals
    • Changedwell_resolve_reconciliation_task4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_resolve_register_diff_gap4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_retarget_connectors4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_run_register_diff4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_search_company_registry4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_search_context4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_search_skill5 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
      • changedOutput schema / properties / reason / anyOf
        Previous 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"
        +  }
        +]
    • Addedwell_set_counterparty_default_category
    • Changedwell_set_own_company4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_set_transaction_category4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_set_transaction_ledger_account4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_show_accounting_settings4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_show_company_candidates4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_show_records4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_show_retargetable_connectors4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_show_workspace_picker5 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
      • changedOutput schema / properties / session / description
        Previous 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."
    • Changedwell_sum_invoices4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_sum_transactions4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_switch_workspace14 fields changed
      • changedInput schema / properties / ack / description
        Previous 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."
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • changedInput schema / properties / counted_account_types / description
        Previous 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."
      • changedInput schema / properties / counterparties / description
        Previous 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."
      • changedInput schema / properties / counterparty_periods / description
        Previous 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."
      • changedInput schema / properties / exempt_categories / description
        Previous 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."
      • changedInput schema / properties / periods / description
        Previous 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."
      • changedInput schema / properties / recurring_contexts / description
        Previous 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."
      • changedInput schema / properties / workspace_ids / description
        Previous 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."
      • changedOutput schema / properties / changed / description
        Previous value: -"Which session fields this call wrote."New value: +"Which fields of this conversation's context this call wrote."
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
      • addedOutput schema / properties / warning
        Added 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"
        +}
    • Changedwell_update_company7 fields changed
      • addedInput schema / properties / account_payable_default_id
        Added 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`."
        +}
      • addedInput schema / properties / account_receivable_default_id
        Added 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."
        +}
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedInput schema / properties / ledger_default_source
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_update_invoice4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_update_person4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_upload_document4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_upload_statement_bytes4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_upload_statement_content4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_upsert_accounting_settings4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
    • Changedwell_wait_for_selection4 fields changed
      • addedInput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id
        Added 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"
        +}
      • addedOutput schema / properties / conversation_id_note
        Added value: +{
        +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / conversation_id_source
        Added 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"
        +}
  2. 8 tool updates
    • Changedwell_claim_statement_draft9 fields changed
      • changedInput schema / properties / claim_token / description
        Previous 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."
      • removedOutput schema / properties / byte_length
        Removed value: -{
        -  "maximum": 9007199254740991,
        -  "minimum": -9007199254740991,
        -  "type": "integer"
        -}
      • removedOutput schema / properties / content_sha256
        Removed value: -{
        -  "description": "SHA-256 (hex) of the payload the server received — compare against your source to verify fidelity.",
        -  "type": "string"
        -}
      • removedOutput schema / properties / deduplicated
        Removed value: -{
        -  "description": "True when an identical document was already in the workspace — nothing was imported twice.",
        -  "type": "boolean"
        -}
      • removedOutput schema / properties / document_id
        Removed value: -{
        -  "description": "Poll well_get_statement_import_result with this id for the import outcome.",
        -  "type": "string"
        -}
      • addedOutput schema / properties / documents
        Added 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"
        +}
      • addedOutput schema / properties / drop_id
        Added value: +{
        +  "description": "The drop this token belonged to. Safe to quote; the token is not.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / failed
        Added 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"
        +}
      • changedOutput schema / required
        Previous value: -[
        -  "success"
        -]New value: +[
        +  "success",
        +  "documents",
        +  "failed"
        +]
    • Removedwell_get_cost_structure
    • Changedwell_invite_members2 fields changed
      • addedInput schema / properties / notify
        Added 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"
        +}
      • addedOutput schema / properties / results / items / properties / person_id
        Added 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"
        +}
    • Changedwell_list_cash_scope1 field changed
      • addedOutput schema / properties / next_step
        Added value: +{
        +  "description": "What to do with the card this result renders. Added by the dispatcher when the card asks for a click.",
        +  "type": "string"
        +}
    • Addedwell_render_cost_structure
    • Changedwell_sum_transactions8 fields changed
      • changedInput schema / properties / axes / items / enum
        Previous value: -[
        -  "month",
        -  "currency",
        -  "category"
        -]New value: +[
        +  "month",
        +  "currency",
        +  "category",
        +  "ledger_account",
        +  "category_label",
        +  "transaction_type"
        +]
      • addedInput schema / properties / axes / maxItems
        Added value: +6
      • addedOutput schema / properties / rows / items / properties / category_label
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / ledger_account
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / transaction_type
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ]
        +}
      • changedOutput schema / properties / rows / items / required
        Previous 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"
        +]
      • addedOutput schema / properties / rows_truncated
        Added value: +{
        +  "type": "boolean"
        +}
      • changedOutput schema / required
        Previous 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"
        +]
    • Changedwell_switch_workspace3 fields changed
      • addedInput schema / properties / counted_account_types
        Added 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"
        +}
      • changedOutput schema / properties / changed / items / enum
        Previous 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"
        +]
      • addedOutput schema / properties / selected_cash_scope
        Added value: +{
        +  "items": {
        +    "type": "string"
        +  },
        +  "type": "array"
        +}
    • Changedwell_wait_for_selection4 fields changed
      • changedInput schema / properties / kind / description
        Previous 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)."
      • changedInput schema / properties / kind / enum
        Previous 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"
        +]
      • changedOutput schema / properties / kind / enum
        Previous 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"
        +]
      • addedOutput schema / properties / selection / properties / counted_account_types
        Added 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"
        +}
  3. 18 tool updates
    • Changedwell_add_contact_channel9 fields changed
      • addedInput schema / properties / value / properties / address_line1 / maxLength
        Added value: +255
      • addedInput schema / properties / value / properties / address_line2 / maxLength
        Added value: +255
      • addedInput schema / properties / value / properties / city / maxLength
        Added value: +255
      • addedInput schema / properties / value / properties / country / maxLength
        Added value: +2
      • addedInput schema / properties / value / properties / e164_number / maxLength
        Added value: +255
      • addedInput schema / properties / value / properties / email / maxLength
        Added value: +320
      • addedInput schema / properties / value / properties / postal_code / maxLength
        Added value: +255
      • addedInput schema / properties / value / properties / region / maxLength
        Added value: +255
      • addedInput schema / properties / value / properties / url / maxLength
        Added value: +255
    • Addedwell_claim_statement_draft
    • Changedwell_create_company1 field changed
      • addedInput schema / properties / description / maxLength
        Added value: +250
    • Changedwell_create_invoice_from_data4 fields changed
      • addedInput schema / properties / issuer / properties / name / maxLength
        Added value: +255
      • addedInput schema / properties / line_items / items / properties / name / maxLength
        Added value: +255
      • addedInput schema / properties / receiver / properties / name / maxLength
        Added value: +255
      • addedInput schema / properties / reference_number / maxLength
        Added value: +100
    • Changedwell_create_person2 fields changed
      • addedInput schema / properties / job_title / maxLength
        Added value: +100
      • changedInput schema / properties / last_name / anyOf
        Previous value: -[
        -  {
        -    "maxLength": 100,
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]New value: +[
        +  {
        +    "maxLength": 255,
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
    • Removedwell_get_cash_forecast
    • Changedwell_get_connector_coverage5 fields changed
      • changedInput schema / properties / kind / description
        Previous 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."
      • changedInput schema / properties / kind / enum
        Previous value: -[
        -  "bank",
        -  "accounting",
        -  "invoicing",
        -  "upload_surface"
        -]New value: +[
        +  "bank",
        +  "accounting",
        +  "invoicing",
        +  "upload_surface",
        +  "storage"
        +]
      • changedOutput schema / properties / connectors / items / properties / connection_status / anyOf
        Previous 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"
        +  }
        +]
      • addedOutput schema / properties / row_count
        Added 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"
        +}
      • changedOutput schema / properties / scope / enum
        Previous value: -[
        -  "catalog",
        -  "bank",
        -  "accounting",
        -  "invoicing",
        -  "upload_surface",
        -  "picked_vendors"
        -]New value: +[
        +  "catalog",
        +  "bank",
        +  "accounting",
        +  "invoicing",
        +  "upload_surface",
        +  "storage",
        +  "picked_vendors"
        +]
    • Addedwell_get_customer_einvoicing_details
    • Changedwell_get_session_digest1 field changed
      • changedOutput schema / properties / situation / properties / connectors / anyOf
        Previous 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"
        +  }
        +]
    • Changedwell_get_statement_import_result2 fields changed
      • changedOutput schema / properties / graph / properties / graph / properties / edges / items / properties / kind / enum
        Previous 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"
        +]
      • changedOutput schema / properties / graph / properties / graph / properties / nodes / items / properties / type / enum
        Previous 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"
        +]
    • Changedwell_get_worklist_status3 fields changed
      • addedInput schema / properties / periods
        Added 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"
        +}
      • changedInput schema / properties / worklist / enum
        Previous value: -[
        -  "accounts_needing_company",
        -  "uncategorized_window",
        -  "unposted_transactions"
        -]New value: +[
        +  "accounts_needing_company",
        +  "uncategorized_window",
        +  "unposted_transactions",
        +  "invoice_sources_for_pick",
        +  "counterparties_to_categorize"
        +]
      • addedOutput schema / properties / count
        Added 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"
        +}
    • Changedwell_list_connectors5 fields changed
      • changedInput schema / properties / kind / description
        Previous 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."
      • changedInput schema / properties / kind / enum
        Previous value: -[
        -  "bank",
        -  "accounting",
        -  "invoicing",
        -  "upload_surface"
        -]New value: +[
        +  "bank",
        +  "accounting",
        +  "invoicing",
        +  "upload_surface",
        +  "storage"
        +]
      • changedOutput schema / properties / connectors / items / properties / connection_status / anyOf
        Previous 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"
        +  }
        +]
      • addedOutput schema / properties / row_count
        Added 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"
        +}
      • changedOutput schema / properties / scope / enum
        Previous value: -[
        -  "catalog",
        -  "bank",
        -  "accounting",
        -  "invoicing",
        -  "upload_surface",
        -  "picked_vendors"
        -]New value: +[
        +  "catalog",
        +  "bank",
        +  "accounting",
        +  "invoicing",
        +  "upload_surface",
        +  "storage",
        +  "picked_vendors"
        +]
    • Addedwell_render_cash_forecast
    • Changedwell_render_runway2 fields changed
      • addedInput schema / properties / window
        Added 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"
        +}
      • addedOutput schema / properties / window
        Added value: +{
        +  "additionalProperties": false,
        +  "properties": {
        +    "from": {
        +      "type": "string"
        +    },
        +    "to": {
        +      "type": "string"
        +    }
        +  },
        +  "required": [
        +    "from",
        +    "to"
        +  ],
        +  "type": "object"
        +}
    • Changedwell_update_company7 fields changed
      • addedInput schema / properties / decision
        Added 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"
        +}
      • changedInput schema / properties / description / anyOf
        Previous value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]New value: +[
        +  {
        +    "maxLength": 250,
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • changedInput schema / properties / domain / anyOf
        Previous value: -[
        -  {
        -    "maxLength": 255,
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]New value: +[
        +  {
        +    "maxLength": 500,
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • changedInput schema / properties / registered_value / anyOf
        Previous value: -[
        -  {
        -    "maxLength": 50,
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]New value: +[
        +  {
        +    "maxLength": 100,
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • changedInput schema / properties / tax_id_value / anyOf
        Previous value: -[
        -  {
        -    "maxLength": 50,
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]New value: +[
        +  {
        +    "maxLength": 255,
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • changedInput schema / properties / trade_name / anyOf
        Previous value: -[
        -  {
        -    "maxLength": 255,
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]New value: +[
        +  {
        +    "maxLength": 100,
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • addedOutput schema / properties / decision
        Added value: +{
        +  "enum": [
        +    "accepted_suggestion",
        +    "explicit"
        +  ],
        +  "type": "string"
        +}
    • Changedwell_update_invoice2 fields changed
      • changedInput schema / properties / description / anyOf
        Previous value: -[
        -  {
        -    "maxLength": 2000,
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]New value: +[
        +  {
        +    "maxLength": 255,
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • changedInput schema / properties / terms / anyOf
        Previous value: -[
        -  {
        -    "maxLength": 1000,
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]New value: +[
        +  {
        +    "maxLength": 300,
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
    • Changedwell_update_person1 field changed
      • changedInput schema / properties / job_title / anyOf
        Previous value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]New value: +[
        +  {
        +    "maxLength": 100,
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
    • Changedwell_upload_document1 field changed
      • removedInput schema / properties / filename / maxLength
        Removed value: -255
  4. 14 tool updates
    • Changedwell_create_company1 field changed
      • removedInput schema / properties / description / maxLength
        Removed value: -1000
    • Changedwell_create_company_candidate1 field changed
      • changedOutput schema / properties / candidate / anyOf
        Previous 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"
        +  }
        +]
    • Changedwell_get_connector_coverage8 fields changed
      • changedInput schema / properties / from_selection / description
        Previous 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."
      • changedInput schema / properties / kind / description
        Previous 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."
      • changedInput schema / properties / kind / enum
        Previous value: -[
        -  "bank",
        -  "accounting",
        -  "invoicing"
        -]New value: +[
        +  "bank",
        +  "accounting",
        +  "invoicing",
        +  "upload_surface"
        +]
      • addedOutput schema / properties / connectors / items / properties / invoice_source
        Added value: +{
        +  "type": "boolean"
        +}
      • addedOutput schema / properties / connectors / items / properties / reason
        Added value: +{
        +  "enum": [
        +    "catalog",
        +    "picked_vendor"
        +  ],
        +  "type": "string"
        +}
      • changedOutput schema / properties / connectors / items / required
        Previous 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"
        +]
      • addedOutput schema / properties / picked_vendors_filtered
        Added 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"
        +}
      • changedOutput schema / properties / scope / enum
        Previous value: -[
        -  "catalog",
        -  "bank",
        -  "accounting",
        -  "invoicing",
        -  "picked_vendors"
        -]New value: +[
        +  "catalog",
        +  "bank",
        +  "accounting",
        +  "invoicing",
        +  "upload_surface",
        +  "picked_vendors"
        +]
    • Changedwell_get_own_company7 fields changed
      • addedOutput schema / properties / candidates / items / properties / business_type
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "description": "The company's legal form (SAS, Inc, GmbH), when known."
        +}
      • addedOutput schema / properties / candidates / items / properties / confirmable
        Added 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"
        +}
      • addedOutput schema / properties / candidates / items / properties / confirmable_reason
        Added 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."
        +}
      • addedOutput schema / properties / candidates / items / properties / country
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "description": "The company's country, for a light detail panel."
        +}
      • addedOutput schema / properties / candidates / items / properties / domain
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "description": "The company's domain, a logo source and a display fallback."
        +}
      • addedOutput schema / properties / candidates / items / properties / remote_logo_url
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "description": "A resolved logo url when one is known; null otherwise."
        +}
      • changedOutput schema / properties / candidates / items / required
        Previous 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"
        +]
    • Changedwell_list_burn_exemptions1 field changed
      • changedOutput schema / properties / partial / description
        Previous 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."
    • Changedwell_list_connectors8 fields changed
      • changedInput schema / properties / from_selection / description
        Previous 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."
      • changedInput schema / properties / kind / description
        Previous 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."
      • changedInput schema / properties / kind / enum
        Previous value: -[
        -  "bank",
        -  "accounting",
        -  "invoicing"
        -]New value: +[
        +  "bank",
        +  "accounting",
        +  "invoicing",
        +  "upload_surface"
        +]
      • addedOutput schema / properties / connectors / items / properties / invoice_source
        Added value: +{
        +  "type": "boolean"
        +}
      • addedOutput schema / properties / connectors / items / properties / reason
        Added value: +{
        +  "enum": [
        +    "catalog",
        +    "picked_vendor"
        +  ],
        +  "type": "string"
        +}
      • changedOutput schema / properties / connectors / items / required
        Previous 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"
        +]
      • addedOutput schema / properties / picked_vendors_filtered
        Added 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"
        +}
      • changedOutput schema / properties / scope / enum
        Previous value: -[
        -  "catalog",
        -  "bank",
        -  "accounting",
        -  "invoicing",
        -  "picked_vendors"
        -]New value: +[
        +  "catalog",
        +  "bank",
        +  "accounting",
        +  "invoicing",
        +  "upload_surface",
        +  "picked_vendors"
        +]
    • Changedwell_list_member_candidates1 field changed
      • addedOutput schema / properties / next_step
        Added value: +{
        +  "description": "What to do with the card this result renders. Added by the dispatcher when the card asks for a click.",
        +  "type": "string"
        +}
    • Changedwell_list_periods2 fields changed
      • changedOutput schema / properties / periods / items / properties / categorized_unposted_transactions / description
        Previous 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."
      • changedOutput schema / properties / periods / items / properties / uncategorized_transactions / description
        Previous 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\"."
    • Addedwell_search_context
    • Changedwell_show_company_candidates7 fields changed
      • addedOutput schema / properties / candidates / items / properties / business_type
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "description": "The company's legal form (SAS, Inc, GmbH), when known."
        +}
      • addedOutput schema / properties / candidates / items / properties / confirmable
        Added 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"
        +}
      • addedOutput schema / properties / candidates / items / properties / confirmable_reason
        Added 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."
        +}
      • addedOutput schema / properties / candidates / items / properties / country
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "description": "The company's country, for a light detail panel."
        +}
      • addedOutput schema / properties / candidates / items / properties / domain
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "description": "The company's domain, a logo source and a display fallback."
        +}
      • addedOutput schema / properties / candidates / items / properties / remote_logo_url
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "description": "A resolved logo url when one is known; null otherwise."
        +}
      • changedOutput schema / properties / candidates / items / required
        Previous 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"
        +]
    • Changedwell_switch_workspace5 fields changed
      • changedInput schema / properties / ack / description
        Previous 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."
      • changedInput schema / properties / ack / enum
        Previous value: -[
        -  "connectors",
        -  "bank",
        -  "categorize",
        -  "assign",
        -  "deploy",
        -  "company_pick",
        -  "accounting_settings",
        -  "retarget"
        -]New value: +[
        +  "connectors",
        +  "bank",
        +  "categorize",
        +  "assign",
        +  "deploy",
        +  "company_pick",
        +  "accounting_settings",
        +  "retarget",
        +  "invite"
        +]
      • changedInput schema / properties / ack_outcome / description
        Previous 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."
      • changedOutput schema / properties / acknowledged / enum
        Previous value: -[
        -  "connectors",
        -  "bank",
        -  "categorize",
        -  "assign",
        -  "deploy",
        -  "company_pick",
        -  "accounting_settings",
        -  "retarget"
        -]New value: +[
        +  "connectors",
        +  "bank",
        +  "categorize",
        +  "assign",
        +  "deploy",
        +  "company_pick",
        +  "accounting_settings",
        +  "retarget",
        +  "invite"
        +]
      • changedOutput schema / properties / changed / items / enum
        Previous 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"
        +]
    • Changedwell_update_company1 field changed
      • changedInput schema / properties / description / anyOf
        Previous value: -[
        -  {
        -    "maxLength": 1000,
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]New value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
    • Addedwell_upload_document
    • Changedwell_wait_for_selection3 fields changed
      • changedInput schema / properties / kind / description
        Previous 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)."
      • changedInput schema / properties / kind / enum
        Previous 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"
        +]
      • changedOutput schema / properties / kind / enum
        Previous 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"
        +]
  5. 29 tool updates
    • Addedwell_create_company_candidate
    • Addedwell_create_company_workspace
    • Changedwell_create_person1 field changed
      • removedInput schema / properties / job_title / maxLength
        Removed value: -200
    • Addedwell_get_accounting_settings
    • Changedwell_get_connector_coverage3 fields changed
      • addedInput schema / properties / country
        Added 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"
        +}
      • addedOutput schema / properties / connectors / items / properties / countries
        Added value: +{
        +  "anyOf": [
        +    {
        +      "items": {
        +        "type": "string"
        +      },
        +      "type": "array"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ]
        +}
      • changedOutput schema / properties / connectors / items / required
        Previous 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"
        +]
    • Changedwell_get_own_company2 fields changed
      • addedOutput schema / properties / candidates / items / properties / candidate_id
        Added value: +{
        +  "description": "The candidate's id — pass to well_create_company_workspace to mint the company workspace from it.",
        +  "type": "string"
        +}
      • changedOutput schema / properties / candidates / items / required
        Previous 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"
        +]
    • Changedwell_get_worklist_status2 fields changed
      • changedInput schema / properties / fiscal_period / description
        Previous 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."
      • changedInput schema / properties / fiscal_period / maximum
        Previous value: -12New value: +13
    • Changedwell_list_connectors3 fields changed
      • addedInput schema / properties / country
        Added 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"
        +}
      • addedOutput schema / properties / connectors / items / properties / countries
        Added value: +{
        +  "anyOf": [
        +    {
        +      "items": {
        +        "type": "string"
        +      },
        +      "type": "array"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ]
        +}
      • changedOutput schema / properties / connectors / items / required
        Previous 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"
        +]
    • Changedwell_list_periods1 field changed
      • addedInput schema / properties / reply
        Added 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"
        +}
    • Addedwell_list_recurring_contexts
    • Addedwell_list_retargetable_connectors
    • Changedwell_list_workspaces6 fields changed
      • addedOutput schema / properties / workspaces / items / properties / identity / properties / country_default_fiscal_year_start_month
        Added 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)."
        +}
      • addedOutput schema / properties / workspaces / items / properties / identity / properties / fiscal_year_start_month_source
        Added 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."
        +}
      • changedOutput schema / properties / workspaces / items / properties / identity / required
        Previous 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"
        +]
      • addedOutput schema / properties / workspaces / items / properties / lineage_parent_workspace_id
        Added 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."
        +}
      • addedOutput schema / properties / workspaces / items / properties / own_company_id
        Added 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."
        +}
      • changedOutput schema / properties / workspaces / items / required
        Previous 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"
        +]
    • Changedwell_render_burn16 fields changed
      • addedInput schema / properties / excluded / properties / internal_transfers / anyOf
        Added value: +[
        +  {
        +    "maximum": 9007199254740991,
        +    "minimum": 0,
        +    "type": "integer"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • addedInput schema / properties / excluded / properties / internal_transfers / description
        Added value: +"The sum's `excluded_multi_leg`. `null` when the sum could not count it, never 0."
      • removedInput schema / properties / excluded / properties / internal_transfers / maximum
        Removed value: -9007199254740991
      • removedInput schema / properties / excluded / properties / internal_transfers / minimum
        Removed value: -0
      • removedInput schema / properties / excluded / properties / internal_transfers / type
        Removed value: -"integer"
      • addedInput schema / properties / unplaceable_count / anyOf
        Added value: +[
        +  {
        +    "maximum": 9007199254740991,
        +    "minimum": 0,
        +    "type": "integer"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • changedInput schema / properties / unplaceable_count / description
        Previous 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."
      • removedInput schema / properties / unplaceable_count / maximum
        Removed value: -9007199254740991
      • removedInput schema / properties / unplaceable_count / minimum
        Removed value: -0
      • removedInput schema / properties / unplaceable_count / type
        Removed value: -"integer"
      • removedOutput schema / properties / caller
        Removed value: -{
        -  "enum": [
        -    "first_party",
        -    "third_party"
        -  ],
        -  "type": "string"
        -}
      • addedOutput schema / properties / excluded / properties / internal_transfers / anyOf
        Added value: +[
        +  {
        +    "type": "number"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedOutput schema / properties / excluded / properties / internal_transfers / type
        Removed value: -"number"
      • addedOutput schema / properties / unplaceable_count / anyOf
        Added value: +[
        +  {
        +    "type": "number"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedOutput schema / properties / unplaceable_count / type
        Removed value: -"number"
      • changedOutput schema / required
        Previous 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"
        +]
    • Changedwell_render_cash_flow_bridge2 fields changed
      • removedOutput schema / properties / caller
        Removed value: -{
        -  "type": "string"
        -}
      • changedOutput schema / required
        Previous 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"
        +]
    • Changedwell_render_cash_position2 fields changed
      • removedOutput schema / properties / caller
        Removed value: -{
        -  "type": "string"
        -}
      • changedOutput schema / required
        Previous 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"
        +]
    • Addedwell_render_mrr
    • Changedwell_render_runway2 fields changed
      • removedOutput schema / properties / caller
        Removed value: -{
        -  "type": "string"
        -}
      • changedOutput schema / required
        Previous 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"
        +]
    • Changedwell_resolve_reconciliation_task1 field changed
      • addedInput schema / properties / selection
        Added 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"
        +}
    • Addedwell_retarget_connectors
    • Addedwell_search_company_registry
    • Addedwell_show_accounting_settings
    • Addedwell_show_company_candidates
    • Addedwell_show_retargetable_connectors
    • Changedwell_show_workspace_picker10 fields changed
      • addedInput schema / properties / reply
        Added 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"
        +}
      • addedInput schema / properties / subtitle
        Added 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"
        +}
      • addedInput schema / properties / title
        Added 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"
        +}
      • addedInput schema / properties / workspace_ids
        Added 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"
        +}
      • addedOutput schema / properties / workspaces / items / properties / identity / properties / country_default_fiscal_year_start_month
        Added 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)."
        +}
      • addedOutput schema / properties / workspaces / items / properties / identity / properties / fiscal_year_start_month_source
        Added 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."
        +}
      • changedOutput schema / properties / workspaces / items / properties / identity / required
        Previous 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"
        +]
      • addedOutput schema / properties / workspaces / items / properties / lineage_parent_workspace_id
        Added 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."
        +}
      • addedOutput schema / properties / workspaces / items / properties / own_company_id
        Added 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."
        +}
      • changedOutput schema / properties / workspaces / items / required
        Previous 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"
        +]
    • Addedwell_sum_invoices
    • Changedwell_switch_workspace7 fields changed
      • changedInput schema / properties / ack / description
        Previous 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."
      • changedInput schema / properties / ack / enum
        Previous value: -[
        -  "connectors",
        -  "bank",
        -  "categorize",
        -  "assign",
        -  "deploy"
        -]New value: +[
        +  "connectors",
        +  "bank",
        +  "categorize",
        +  "assign",
        +  "deploy",
        +  "company_pick",
        +  "accounting_settings",
        +  "retarget"
        +]
      • changedInput schema / properties / ack_outcome / description
        Previous 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."
      • addedInput schema / properties / recurring_contexts
        Added 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"
        +}
      • changedOutput schema / properties / acknowledged / enum
        Previous value: -[
        -  "connectors",
        -  "bank",
        -  "categorize",
        -  "assign",
        -  "deploy"
        -]New value: +[
        +  "connectors",
        +  "bank",
        +  "categorize",
        +  "assign",
        +  "deploy",
        +  "company_pick",
        +  "accounting_settings",
        +  "retarget"
        +]
      • changedOutput schema / properties / changed / items / enum
        Previous 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"
        +]
      • addedOutput schema / properties / selected_recurring_contexts
        Added value: +{
        +  "items": {
        +    "type": "string"
        +  },
        +  "type": "array"
        +}
    • Changedwell_update_person1 field changed
      • changedInput schema / properties / job_title / anyOf
        Previous value: -[
        -  {
        -    "maxLength": 200,
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]New value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
    • Changedwell_upsert_accounting_settings6 fields changed
      • addedInput schema / properties / incorporation_date
        Added 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."
        +}
      • addedInput schema / properties / tax_id_type
        Added value: +{
        +  "description": "The tax id's type (SIREN, VAT, EIN, …). Provide it together with tax_id_value.",
        +  "type": "string"
        +}
      • addedInput schema / properties / tax_id_value
        Added 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"
        +}
      • addedOutput schema / properties / incorporation_date
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ]
        +}
      • addedOutput schema / properties / tax_id_type
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ]
        +}
      • addedOutput schema / properties / tax_id_value
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ]
        +}
    • Changedwell_wait_for_selection5 fields changed
      • changedInput schema / properties / kind / description
        Previous 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)."
      • changedInput schema / properties / kind / enum
        Previous 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"
        +]
      • addedInput schema / properties / waiting_notice
        Added 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"
        +}
      • changedOutput schema / properties / kind / enum
        Previous 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"
        +]
      • addedOutput schema / properties / selection / properties / recurring_contexts
        Added 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"
        +}
  6. 17 tool updates
    • Changedwell_enqueue_invoice_fetch1 field changed
      • changedOutput schema / properties / enqueued / items / properties / provider / anyOf
        Previous 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"
        +  }
        +]
    • Removedwell_get_cash_flow_bridge
    • Addedwell_get_connector_coverage
    • Addedwell_get_session_digest
    • Addedwell_get_worklist_status
    • Changedwell_list_account_balances2 fields changed
      • addedOutput schema / properties / rows / items / properties / duplicate_of_account_id
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ]
        +}
      • changedOutput schema / properties / rows / items / required
        Previous 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"
        +]
    • Changedwell_list_cash_scope2 fields changed
      • addedOutput schema / properties / folded_duplicates
        Added value: +{
        +  "type": "number"
        +}
      • changedOutput schema / required
        Previous 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"
        +]
    • Changedwell_list_missing_invoices2 fields changed
      • changedOutput schema / properties / rows / items / properties / mode / description
        Previous 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."
      • changedOutput schema / properties / rows / items / properties / suggested_action / description
        Previous 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."
    • Changedwell_list_workspaces1 field changed
      • removedOutput schema / properties / next_step
        Removed value: -{
        -  "description": "What to do with the card this result renders. Added by the dispatcher when the card asks for a click.",
        -  "type": "string"
        -}
    • Addedwell_propose_next_steps
    • Changedwell_query_records1 field changed
      • changedInput schema / properties / fields / description
        Previous 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."
    • Addedwell_render_cash_flow_bridge
    • Addedwell_show_records
    • Addedwell_show_workspace_picker
    • Changedwell_sum_transactions4 fields changed
      • addedInput schema / properties / scope
        Added 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"
        +}
      • changedInput schema / required
        Previous value: -[
        -  "from",
        -  "to"
        -]New value: +[
        +  "from",
        +  "to",
        +  "scope"
        +]
      • addedOutput schema / properties / excluded_no_owned_leg
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "number"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ]
        +}
      • changedOutput schema / required
        Previous 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"
        +]
    • Changedwell_switch_workspace3 fields changed
      • addedInput schema / properties / next_step
        Added 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"
        +}
      • changedOutput schema / properties / changed / items / enum
        Previous 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"
        +]
      • addedOutput schema / properties / selected_next_step
        Added 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"
        +}
    • Changedwell_wait_for_selection4 fields changed
      • changedInput schema / properties / kind / description
        Previous 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)."
      • changedInput schema / properties / kind / enum
        Previous 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"
        +]
      • changedOutput schema / properties / kind / enum
        Previous 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"
        +]
      • addedOutput schema / properties / selection / properties / next_step
        Added 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"
        +}
  7. 18 tool updates
    • Changedwell_enqueue_invoice_fetch1 field changed
      • changedOutput schema / properties / skipped / items / properties / reason / enum
        Previous value: -[
        -  "company_not_found_in_workspace"
        -]New value: +[
        +  "company_not_found_in_workspace",
        +  "not_owned_by_current_user"
        +]
    • Removedwell_get_cash_position
    • Removedwell_get_runway
    • Addedwell_list_account_balances
    • Changedwell_list_burn_exemptions1 field changed
      • addedOutput schema / properties / next_step
        Added value: +{
        +  "description": "What to do with the card this result renders. Added by the dispatcher when the card asks for a click.",
        +  "type": "string"
        +}
    • Addedwell_list_cash_scope
    • Changedwell_list_connectors1 field changed
      • addedOutput schema / properties / next_step
        Added value: +{
        +  "description": "What to do with the card this result renders. Added by the dispatcher when the card asks for a click.",
        +  "type": "string"
        +}
    • Changedwell_list_counterparties1 field changed
      • addedOutput schema / properties / next_step
        Added value: +{
        +  "description": "What to do with the card this result renders. Added by the dispatcher when the card asks for a click.",
        +  "type": "string"
        +}
    • Changedwell_list_member_candidates5 fields changed
      • addedInput schema / properties / calendar_month
        Added value: +{
        +  "description": "Calendar month, 1 = January … 12 = December. `from_assigned_gaps` only, paired with `calendar_year`.",
        +  "maximum": 12,
        +  "minimum": 1,
        +  "type": "integer"
        +}
      • addedInput schema / properties / calendar_year
        Added value: +{
        +  "description": "Calendar year, e.g. 2026. `from_assigned_gaps` only.",
        +  "maximum": 2100,
        +  "minimum": 2000,
        +  "type": "integer"
        +}
      • addedInput schema / properties / fiscal_period
        Added 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"
        +}
      • addedInput schema / properties / fiscal_year
        Added value: +{
        +  "description": "Fiscal year (the calendar year the workspace's fiscal year STARTED in). `from_assigned_gaps` only.",
        +  "maximum": 2100,
        +  "minimum": 2000,
        +  "type": "integer"
        +}
      • addedInput schema / properties / from_assigned_gaps
        Added 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"
        +}
    • Changedwell_list_missing_invoice_owners4 fields changed
      • addedOutput schema / properties / next_step
        Added value: +{
        +  "description": "What to do with the card this result renders. Added by the dispatcher when the card asks for a click.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / transactions / items / properties / counterparty / properties / matched_connector_service_id
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "description": "The connector standing for a `connect` route; null for a route with no matched connector."
        +}
      • addedOutput schema / properties / transactions / items / properties / counterparty / properties / mode
        Added 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."
        +}
      • changedOutput schema / properties / transactions / items / properties / counterparty / required
        Previous value: -[
        -  "company_id",
        -  "name",
        -  "logo_url"
        -]New value: +[
        +  "company_id",
        +  "name",
        +  "logo_url",
        +  "mode",
        +  "matched_connector_service_id"
        +]
    • Changedwell_list_missing_invoices1 field changed
      • addedOutput schema / properties / next_step
        Added value: +{
        +  "description": "What to do with the card this result renders. Added by the dispatcher when the card asks for a click.",
        +  "type": "string"
        +}
    • Changedwell_list_periods4 fields changed
      • changedInput schema / properties / purpose / description
        Previous 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."
      • addedOutput schema / properties / next_step
        Added value: +{
        +  "description": "What to do with the card this result renders. Added by the dispatcher when the card asks for a click.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / periods / items / properties / analysis_days
        Added 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"
        +}
      • changedOutput schema / properties / periods / items / properties / days / description
        Previous 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."
    • Changedwell_list_workspaces1 field changed
      • addedOutput schema / properties / next_step
        Added value: +{
        +  "description": "What to do with the card this result renders. Added by the dispatcher when the card asks for a click.",
        +  "type": "string"
        +}
    • Changedwell_preview_invoice_fetch2 fields changed
      • addedOutput schema / properties / next_step
        Added value: +{
        +  "description": "What to do with the card this result renders. Added by the dispatcher when the card asks for a click.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / rows_dropped_by_ownership
        Added 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"
        +}
    • Addedwell_render_cash_position
    • Addedwell_render_runway
    • Changedwell_switch_workspace9 fields changed
      • changedInput schema / properties / ack / description
        Previous 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."
      • changedInput schema / properties / ack / enum
        Previous value: -[
        -  "connectors",
        -  "bank"
        -]New value: +[
        +  "connectors",
        +  "bank",
        +  "categorize",
        +  "assign",
        +  "deploy"
        +]
      • addedInput schema / properties / ack_outcome
        Added 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"
        +}
      • changedOutput schema / properties / acknowledged / enum
        Previous value: -[
        -  "connectors",
        -  "bank"
        -]New value: +[
        +  "connectors",
        +  "bank",
        +  "categorize",
        +  "assign",
        +  "deploy"
        +]
      • addedOutput schema / properties / acknowledged_outcome
        Added value: +{
        +  "description": "The answer the acknowledging click carried, echoed back on a step whose card offers two.",
        +  "enum": [
        +    "done",
        +    "keep_for_later"
        +  ],
        +  "type": "string"
        +}
      • changedOutput schema / properties / changed / items / enum
        Previous 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"
        +]
      • addedOutput schema / properties / pickup
        Added 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"
        +}
      • addedOutput schema / properties / resumed
        Added 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"
        +}
      • changedOutput schema / required
        Previous value: -[
        -  "success"
        -]New value: +[
        +  "success",
        +  "resumed",
        +  "pickup"
        +]
    • Changedwell_wait_for_selection6 fields changed
      • changedInput schema / properties / kind / description
        Previous 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)."
      • changedInput schema / properties / kind / enum
        Previous 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"
        +]
      • changedInput schema / properties / timeout_s / description
        Previous 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."
      • changedOutput schema / properties / already_set / description
        Previous 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."
      • changedOutput schema / properties / kind / enum
        Previous 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"
        +]
      • addedOutput schema / properties / selection / properties / outcome
        Added 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"
        +}
  8. 4 tool updates
    • Addedwell_list_accounts_needing_company
    • Addedwell_list_unposted_transactions
    • Addedwell_set_transaction_category
    • Addedwell_set_transaction_ledger_account
  9. 7 tool updates
    • Addedwell_assign_missing_invoice_owners
    • Addedwell_get_skill
    • Addedwell_invite_members
    • Addedwell_list_member_candidates
    • Addedwell_list_missing_invoice_owners
    • Changedwell_list_missing_invoices2 fields changed
      • addedOutput schema / properties / rows / items / properties / matched_provider_url
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ]
        +}
      • changedOutput schema / properties / rows / items / required
        Previous 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"
        +]
    • Addedwell_search_skill
  10. 3 tool updates
    • Addedwell_enqueue_invoice_fetch
    • Changedwell_list_missing_invoices1 field changed
      • changedOutput schema / properties / rows / items / properties / logo_url / description
        Previous 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."
    • Changedwell_preview_invoice_fetch1 field changed
      • changedOutput schema / properties / vendors / items / properties / logo_url / description
        Previous 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`."
  11. 13 tool updates
    • Addedwell_assign_account
    • Removedwell_get_burn
    • Changedwell_get_cost_structure3 fields changed
      • addedOutput schema / properties / entries / items / properties / category_key
        Added 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"
        +}
      • changedOutput schema / properties / rung / description
        Previous 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)."
      • changedOutput schema / properties / rung / enum
        Previous value: -[
        -  "ledger_account",
        -  "category_normalized",
        -  "transaction_type",
        -  "uncategorised"
        -]New value: +[
        +  "ledger_account",
        +  "category_key",
        +  "category_normalized",
        +  "transaction_type",
        +  "uncategorised"
        +]
    • Changedwell_get_statement_import_result3 fields changed
      • addedOutput schema / properties / counterparties_pending
        Added 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"
        +}
      • addedOutput schema / properties / phase
        Added value: +{
        +  "anyOf": [
        +    {
        +      "enum": [
        +        "queued",
        +        "extracting",
        +        "categorized",
        +        "importing"
        +      ],
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ]
        +}
      • addedOutput schema / properties / reason
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ]
        +}
    • Addedwell_list_burn_exemptions
    • Changedwell_list_connectors4 fields changed
      • addedOutput schema / properties / limit / description
        Added value: +"The page size that was REQUESTED. The catalog may return fewer."
      • addedOutput schema / properties / offset / description
        Added value: +"How many catalog rows this page skipped."
      • addedOutput schema / properties / page_count
        Added 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"
        +}
      • addedOutput schema / properties / total / description
        Added value: +"Every connector matching the query, across all pages — NOT the length of `connectors`."
    • Changedwell_list_missing_invoices2 fields changed
      • addedOutput schema / properties / rows / items / properties / domain_resolution
        Added 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"
        +}
      • changedOutput schema / properties / rows / items / required
        Previous 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"
        +]
    • Changedwell_list_periods10 fields changed
      • changedInput schema / properties / purpose / description
        Previous 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."
      • changedInput schema / properties / purpose / enum
        Previous value: -[
        -  "close",
        -  "collect"
        -]New value: +[
        +  "close",
        +  "collect",
        +  "analysis"
        +]
      • addedOutput schema / properties / periods / items / properties / analyzable
        Added 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"
        +}
      • changedOutput schema / properties / periods / items / properties / categorized_unposted_transactions / description
        Previous 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."
      • changedOutput schema / properties / periods / items / properties / days / description
        Previous 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."
      • addedOutput schema / properties / periods / items / properties / invoice_state / description
        Added 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."
      • changedOutput schema / properties / periods / items / properties / missing_invoice_count / description
        Previous 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."
      • addedOutput schema / properties / periods / items / properties / transaction_count / description
        Added 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."
      • changedOutput schema / properties / periods / items / properties / uncategorized_transactions / description
        Previous 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."
      • changedOutput schema / properties / periods / items / required
        Previous 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"
        +]
    • Addedwell_list_uncategorized_window
    • Addedwell_render_burn
    • Addedwell_sum_transactions
    • Changedwell_switch_workspace3 fields changed
      • addedInput schema / properties / exempt_categories
        Added 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"
        +}
      • changedOutput schema / properties / changed / items / enum
        Previous value: -[
        -  "workspace",
        -  "periods",
        -  "counterparties",
        -  "connectors_ack",
        -  "bank_ack"
        -]New value: +[
        +  "workspace",
        +  "periods",
        +  "counterparties",
        +  "exemptions",
        +  "connectors_ack",
        +  "bank_ack"
        +]
      • addedOutput schema / properties / selected_exemptions
        Added value: +{
        +  "items": {
        +    "type": "string"
        +  },
        +  "type": "array"
        +}
    • Changedwell_wait_for_selection4 fields changed
      • changedInput schema / properties / kind / description
        Previous 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)."
      • changedInput schema / properties / kind / enum
        Previous value: -[
        -  "workspace",
        -  "periods",
        -  "counterparties",
        -  "connect_ack",
        -  "bank_ack"
        -]New value: +[
        +  "workspace",
        +  "periods",
        +  "counterparties",
        +  "exemptions",
        +  "connect_ack",
        +  "bank_ack"
        +]
      • changedOutput schema / properties / kind / enum
        Previous value: -[
        -  "workspace",
        -  "periods",
        -  "counterparties",
        -  "connect_ack",
        -  "bank_ack"
        -]New value: +[
        +  "workspace",
        +  "periods",
        +  "counterparties",
        +  "exemptions",
        +  "connect_ack",
        +  "bank_ack"
        +]
      • addedOutput schema / properties / selection / properties / exempt_categories
        Added 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"
        +}
  12. 7 tool updates
    • Changedwell_list_counterparties9 fields changed
      • changedInput schema / properties / cursor / description
        Previous 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."
      • changedInput schema / properties / uncategorized_only / description
        Previous 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."
      • changedOutput schema / properties / categorized_count / description
        Previous 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."
      • changedOutput schema / properties / mode / description
        Previous 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."
      • addedOutput schema / properties / rows / items / properties / suggested_categories
        Added 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"
        +}
      • changedOutput schema / properties / rows / items / required
        Previous 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"
        +]
      • changedOutput schema / properties / total_count / description
        Previous 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`."
      • changedOutput schema / properties / uncategorized_count / description
        Previous 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."
      • addedOutput schema / properties / uncategorized_only
        Added 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"
        +}
    • Changedwell_list_missing_invoices15 fields changed
      • addedOutput schema / properties / months / items / properties / row_count / description
        Added 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."
      • addedOutput schema / properties / row_count / description
        Added value: +"Rows in `rows`, which is the DISTINCT counterparties the call found. Never the sum of the months' own `row_count`."
      • changedOutput schema / properties / rows / description
        Previous 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."
      • removedOutput schema / properties / rows / items / properties / acquisition_status
        Removed value: -{
        -  "enum": [
        -    "waiting",
        -    "processing",
        -    "mapped",
        -    "refused"
        -  ],
        -  "type": "string"
        -}
      • changedOutput schema / properties / rows / items / properties / base_total_amount / description
        Previous 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."
      • removedOutput schema / properties / rows / items / properties / calendar_month
        Removed value: -{
        -  "maximum": 9007199254740991,
        -  "minimum": -9007199254740991,
        -  "type": "integer"
        -}
      • removedOutput schema / properties / rows / items / properties / calendar_year
        Removed value: -{
        -  "maximum": 9007199254740991,
        -  "minimum": -9007199254740991,
        -  "type": "integer"
        -}
      • changedOutput schema / properties / rows / items / properties / id / description
        Previous 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."
      • addedOutput schema / properties / rows / items / properties / months
        Added 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"
        +}
      • removedOutput schema / properties / rows / items / properties / period_label
        Removed value: -{
        -  "description": "The month this row belongs to, e.g. \"June 2026\".",
        -  "type": "string"
        -}
      • removedOutput schema / properties / rows / items / properties / proof_task_id
        Removed value: -{
        -  "anyOf": [
        -    {
        -      "type": "string"
        -    },
        -    {
        -      "type": "null"
        -    }
        -  ],
        -  "description": "The close-proof task bound to this gap; null until one is minted."
        -}
      • removedOutput schema / properties / rows / items / properties / refusal_reason
        Removed 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."
        -}
      • changedOutput schema / properties / rows / items / properties / transactions / description
        Previous 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."
      • addedOutput schema / properties / rows / items / properties / tx_count / description
        Added value: +"Transactions missing an invoice, summed across every month in `months`."
      • changedOutput schema / properties / rows / items / required
        Previous 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"
        +]
    • Changedwell_list_periods1 field changed
      • changedOutput schema / properties / periods / items / properties / inspectable / description
        Previous 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."
    • Changedwell_list_workspaces2 fields changed
      • addedOutput schema / properties / workspaces / items / properties / has_bank_transactions
        Added 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."
        +}
      • changedOutput schema / properties / workspaces / items / required
        Previous value: -[
        -  "workspace_id",
        -  "workspace_name",
        -  "is_primary",
        -  "identity"
        -]New value: +[
        +  "workspace_id",
        +  "workspace_name",
        +  "is_primary",
        +  "has_bank_transactions",
        +  "identity"
        +]
    • Changedwell_preview_invoice_fetch12 fields changed
      • removedOutput schema / properties / agents
        Removed 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"
        -}
      • changedOutput schema / properties / collect_url / description
        Previous 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."
      • addedOutput schema / properties / collect_url_unaddressable
        Added 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"
        +}
      • changedOutput schema / properties / counts / description
        Previous 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."
      • changedOutput schema / properties / counts / properties / agents / description
        Previous 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."
      • addedOutput schema / properties / counts / properties / vendors
        Added value: +{
        +  "description": "Vendors the card lists — one per distinct portal, or per counterparty where none matched.",
        +  "maximum": 9007199254740991,
        +  "minimum": -9007199254740991,
        +  "type": "integer"
        +}
      • changedOutput schema / properties / counts / required
        Previous value: -[
        -  "agents",
        -  "agent_tx",
        -  "upload",
        -  "connect"
        -]New value: +[
        +  "vendors",
        +  "agents",
        +  "agent_tx",
        +  "upload",
        +  "connect"
        +]
      • changedOutput schema / properties / months / items / properties / counts / properties / agents / description
        Previous 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."
      • addedOutput schema / properties / months / items / properties / counts / properties / vendors
        Added value: +{
        +  "description": "Vendors the card lists — one per distinct portal, or per counterparty where none matched.",
        +  "maximum": 9007199254740991,
        +  "minimum": -9007199254740991,
        +  "type": "integer"
        +}
      • changedOutput schema / properties / months / items / properties / counts / required
        Previous value: -[
        -  "agents",
        -  "agent_tx",
        -  "upload",
        -  "connect"
        -]New value: +[
        +  "vendors",
        +  "agents",
        +  "agent_tx",
        +  "upload",
        +  "connect"
        +]
      • addedOutput schema / properties / vendors
        Added 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"
        +}
      • changedOutput schema / required
        Previous value: -[
        -  "agents",
        -  "upload_rows",
        -  "connect_rows",
        -  "success"
        -]New value: +[
        +  "vendors",
        +  "upload_rows",
        +  "connect_rows",
        +  "success"
        +]
    • Addedwell_upload_statement_bytes
    • Addedwell_upload_statement_content
  13. 8 tool updates
    • Changedwell_get_burn2 fields changed
      • addedOutput schema / properties / hints / items / properties / cta_link
        Added value: +{
        +  "format": "uri",
        +  "type": "string"
        +}
      • addedOutput schema / properties / hints / items / properties / signal_id
        Added value: +{
        +  "type": "string"
        +}
    • Changedwell_get_cash_flow_bridge2 fields changed
      • addedOutput schema / properties / hints / items / properties / cta_link
        Added value: +{
        +  "format": "uri",
        +  "type": "string"
        +}
      • addedOutput schema / properties / hints / items / properties / signal_id
        Added value: +{
        +  "type": "string"
        +}
    • Changedwell_get_cash_forecast2 fields changed
      • addedOutput schema / properties / hints / items / properties / cta_link
        Added value: +{
        +  "format": "uri",
        +  "type": "string"
        +}
      • addedOutput schema / properties / hints / items / properties / signal_id
        Added value: +{
        +  "type": "string"
        +}
    • Changedwell_get_cash_position2 fields changed
      • addedOutput schema / properties / hints / items / properties / cta_link
        Added value: +{
        +  "format": "uri",
        +  "type": "string"
        +}
      • addedOutput schema / properties / hints / items / properties / signal_id
        Added value: +{
        +  "type": "string"
        +}
    • Changedwell_get_cost_structure2 fields changed
      • addedOutput schema / properties / hints / items / properties / cta_link
        Added value: +{
        +  "format": "uri",
        +  "type": "string"
        +}
      • addedOutput schema / properties / hints / items / properties / signal_id
        Added value: +{
        +  "type": "string"
        +}
    • Changedwell_get_runway2 fields changed
      • addedOutput schema / properties / hints / items / properties / cta_link
        Added value: +{
        +  "format": "uri",
        +  "type": "string"
        +}
      • addedOutput schema / properties / hints / items / properties / signal_id
        Added value: +{
        +  "type": "string"
        +}
    • Changedwell_get_statement_import_result14 fields changed
      • addedOutput schema / properties / records / items / properties / amount / anyOf
        Added value: +[
        +  {
        +    "type": "number"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedOutput schema / properties / records / items / properties / amount / type
        Removed value: -"number"
      • addedOutput schema / properties / records / items / properties / balanceAtFrom
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ]
        +}
      • addedOutput schema / properties / records / items / properties / category
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ]
        +}
      • addedOutput schema / properties / records / items / properties / closingBooked
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "number"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ]
        +}
      • addedOutput schema / properties / records / items / properties / currency / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedOutput schema / properties / records / items / properties / currency / type
        Removed value: -"string"
      • addedOutput schema / properties / records / items / properties / domain
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ]
        +}
      • addedOutput schema / properties / records / items / properties / openingBooked
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "number"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ]
        +}
      • addedOutput schema / properties / records / items / properties / primaryBadge
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ]
        +}
      • addedOutput schema / properties / records / items / properties / receiver
        Added value: +{
        +  "anyOf": [
        +    {
        +      "additionalProperties": false,
        +      "properties": {
        +        "logo_url": {
        +          "anyOf": [
        +            {
        +              "type": "string"
        +            },
        +            {
        +              "type": "null"
        +            }
        +          ]
        +        },
        +        "name": {
        +          "type": "string"
        +        }
        +      },
        +      "required": [
        +        "name",
        +        "logo_url"
        +      ],
        +      "type": "object"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ]
        +}
      • addedOutput schema / properties / records / items / properties / root
        Added 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"
        +}
      • addedOutput schema / properties / records / items / properties / secondaryBadge
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ]
        +}
      • changedOutput schema / properties / records / items / required
        Previous value: -[
        -  "id",
        -  "date",
        -  "label",
        -  "amount",
        -  "currency",
        -  "counterparty"
        -]New value: +[
        +  "root",
        +  "id",
        +  "date",
        +  "label",
        +  "amount",
        +  "currency",
        +  "counterparty",
        +  "receiver",
        +  "category",
        +  "primaryBadge",
        +  "secondaryBadge",
        +  "openingBooked",
        +  "closingBooked",
        +  "balanceAtFrom",
        +  "domain"
        +]
    • Changedwell_list_periods4 fields changed
      • addedOutput schema / properties / periods / items / properties / bank_transaction_count
        Added 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"
        +}
      • addedOutput schema / properties / periods / items / properties / inspectable
        Added 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"
        +}
      • addedOutput schema / properties / periods / items / properties / selectable / description
        Added value: +"The month can be CLOSED. False while it is still running."
      • changedOutput schema / properties / periods / items / required
        Previous 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"
        +]
  14. 2 tool updates
    • Changedwell_create_statement_upload1 field changed
      • addedOutput schema / properties / document_id
        Added value: +{
        +  "description": "The pre-allocated document id — poll well_get_statement_import_result with it.",
        +  "type": "string"
        +}
    • Addedwell_get_statement_import_result
  15. 40 tool updates
    • Changedwell_add_contact_channel1 field changed
      • changedInput schema / properties / workspace_id / description
        Previous 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."
    • Changedwell_create_company1 field changed
      • changedInput schema / properties / workspace_id / description
        Previous 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."
    • Changedwell_create_invoice_document1 field changed
      • changedInput schema / properties / workspace_id / description
        Previous 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."
    • Changedwell_create_invoice_from_data1 field changed
      • changedInput schema / properties / workspace_id / description
        Previous 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."
    • Changedwell_create_person1 field changed
      • changedInput schema / properties / workspace_id / description
        Previous 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."
    • Changedwell_create_statement_upload1 field changed
      • changedInput schema / properties / workspace_id / description
        Previous 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."
    • Changedwell_delete_company1 field changed
      • changedInput schema / properties / workspace_id / description
        Previous 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."
    • Changedwell_delete_invoice1 field changed
      • changedInput schema / properties / workspace_id / description
        Previous 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."
    • Changedwell_delete_person1 field changed
      • changedInput schema / properties / workspace_id / description
        Previous 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."
    • Changedwell_get_burn6 fields changed
      • changedInput schema / properties / workspace_id / description
        Previous 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."
      • addedOutput schema / properties / baseline
        Added 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"
        +}
      • addedOutput schema / properties / change / description
        Added 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."
      • addedOutput schema / properties / resolved_workspace
        Added 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"
        +}
      • addedOutput schema / properties / trend / description
        Added 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."
      • addedOutput schema / properties / trend_polarity
        Added 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"
        +}
    • Changedwell_get_cash_flow_bridge5 fields changed
      • changedInput schema / properties / workspace_id / description
        Previous 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."
      • addedOutput schema / properties / period_end
        Added value: +{
        +  "description": "Inclusive YYYY-MM-DD end of that window.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / period_start
        Added value: +{
        +  "description": "Inclusive YYYY-MM-DD start of the window the flow bars cover (a trailing multi-month span).",
        +  "type": "string"
        +}
      • addedOutput schema / properties / resolved_workspace
        Added 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"
        +}
      • changedOutput schema / properties / steps / items / properties / kind / enum
        Previous value: -[
        -  "start",
        -  "increase",
        -  "decrease",
        -  "total"
        -]New value: +[
        +  "start",
        +  "increase",
        +  "decrease",
        +  "total",
        +  "unexplained"
        +]
    • Changedwell_get_cash_forecast3 fields changed
      • changedInput schema / properties / workspace_id / description
        Previous 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."
      • addedOutput schema / properties / anchor
        Added 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"
        +}
      • addedOutput schema / properties / resolved_workspace
        Added 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"
        +}
    • Changedwell_get_cash_position2 fields changed
      • changedInput schema / properties / workspace_id / description
        Previous 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."
      • addedOutput schema / properties / resolved_workspace
        Added 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"
        +}
    • Changedwell_get_cost_structure3 fields changed
      • changedInput schema / properties / workspace_id / description
        Previous 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."
      • addedOutput schema / properties / label_provenance
        Added 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"
        +}
      • addedOutput schema / properties / resolved_workspace
        Added 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"
        +}
    • Changedwell_get_design_tokens1 field changed
      • changedInput schema / properties / workspace_id / description
        Previous 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."
    • Changedwell_get_entity2 fields changed
      • changedInput schema / properties / workspace_id / description
        Previous 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."
      • addedOutput schema / properties / resolved_workspace
        Added 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"
        +}
    • Changedwell_get_investment_holdings2 fields changed
      • changedInput schema / properties / workspace_id / description
        Previous 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."
      • addedOutput schema / properties / resolved_workspace
        Added 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"
        +}
    • Changedwell_get_own_company2 fields changed
      • changedInput schema / properties / workspace_id / description
        Previous 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."
      • addedOutput schema / properties / resolved_workspace
        Added 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"
        +}
    • Changedwell_get_runway7 fields changed
      • changedInput schema / properties / workspace_id / description
        Previous 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."
      • addedOutput schema / properties / baseline
        Added 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"
        +}
      • addedOutput schema / properties / change / description
        Added value: +"Signed percentage change of `months` against `baseline.value`."
      • removedOutput schema / properties / forecast
        Removed 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"
        -}
      • addedOutput schema / properties / resolved_workspace
        Added 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"
        +}
      • addedOutput schema / properties / trend / description
        Added 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."
      • addedOutput schema / properties / trend_polarity
        Added 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"
        +}
    • Changedwell_get_schema1 field changed
      • changedInput schema / properties / workspace_id / description
        Previous 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."
    • Changedwell_invoke_connector_tool1 field changed
      • changedInput schema / properties / workspace_id / description
        Previous 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."
    • Changedwell_list_connector_tools2 fields changed
      • changedInput schema / properties / workspace_id / description
        Previous 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."
      • addedOutput schema / properties / resolved_workspace
        Added 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"
        +}
    • Changedwell_list_connectors7 fields changed
      • addedInput schema / properties / subtitle
        Added 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"
        +}
      • addedInput schema / properties / title
        Added 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"
        +}
      • changedInput schema / properties / workspace_id / description
        Previous 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."
      • addedOutput schema / properties / install_all_omitted
        Added 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"
        +}
      • addedOutput schema / properties / install_all_url
        Added 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."
        +}
      • addedOutput schema / properties / resolved_workspace
        Added 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"
        +}
      • changedOutput schema / required
        Previous value: -[
        -  "success"
        -]New value: +[
        +  "install_all_url",
        +  "install_all_omitted",
        +  "success"
        +]
    • Changedwell_list_counterparties5 fields changed
      • addedInput schema / properties / cursor
        Added 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"
        +}
      • changedInput schema / properties / uncategorized_only / description
        Previous 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."
      • changedInput schema / properties / workspace_id / description
        Previous 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."
      • addedOutput schema / properties / next_cursor
        Added 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."
        +}
      • addedOutput schema / properties / resolved_workspace
        Added 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"
        +}
    • Changedwell_list_missing_invoices2 fields changed
      • changedInput schema / properties / workspace_id / description
        Previous 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."
      • addedOutput schema / properties / resolved_workspace
        Added 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"
        +}
    • Changedwell_list_periods11 fields changed
      • changedInput schema / properties / months / description
        Previous 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."
      • addedInput schema / properties / purpose
        Added 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"
        +}
      • changedInput schema / properties / workspace_id / description
        Previous 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."
      • addedInput schema / properties / year
        Added 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"
        +}
      • addedOutput schema / properties / navigable_years
        Added 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"
        +}
      • addedOutput schema / properties / periods / items / properties / categorized_unposted_transactions
        Added value: +{
        +  "description": "Categorized transactions not yet posted to the ledger — part of the 'review and book' close errand.",
        +  "type": "number"
        +}
      • addedOutput schema / properties / periods / items / properties / close_days
        Added 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"
        +}
      • addedOutput schema / properties / periods / items / properties / uncategorized_transactions
        Added value: +{
        +  "description": "Transactions in the month not yet categorized — the 'help categorize' close errand.",
        +  "type": "number"
        +}
      • changedOutput schema / properties / periods / items / required
        Previous 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"
        +]
      • addedOutput schema / properties / resolved_workspace
        Added 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"
        +}
      • changedOutput schema / required
        Previous 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"
        +]
    • Changedwell_list_workspaces1 field changed
      • changedInput schema / properties / workspace_id / description
        Previous 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."
    • Changedwell_preview_invoice_fetch2 fields changed
      • changedInput schema / properties / workspace_id / description
        Previous 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."
      • addedOutput schema / properties / resolved_workspace
        Added 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"
        +}
    • Changedwell_query_records1 field changed
      • changedInput schema / properties / workspace_id / description
        Previous 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."
    • Changedwell_remove_contact_channel1 field changed
      • changedInput schema / properties / workspace_id / description
        Previous 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."
    • Changedwell_resolve_reconciliation_task1 field changed
      • changedInput schema / properties / workspace_id / description
        Previous 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."
    • Changedwell_resolve_register_diff_gap1 field changed
      • changedInput schema / properties / workspace_id / description
        Previous 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."
    • Changedwell_run_register_diff1 field changed
      • changedInput schema / properties / workspace_id / description
        Previous 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."
    • Changedwell_set_own_company1 field changed
      • changedInput schema / properties / workspace_id / description
        Previous 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."
    • Changedwell_switch_workspace1 field changed
      • changedInput schema / properties / workspace_id / description
        Previous 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."
    • Changedwell_update_company1 field changed
      • changedInput schema / properties / workspace_id / description
        Previous 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."
    • Changedwell_update_invoice1 field changed
      • changedInput schema / properties / workspace_id / description
        Previous 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."
    • Changedwell_update_person1 field changed
      • changedInput schema / properties / workspace_id / description
        Previous 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."
    • Changedwell_upsert_accounting_settings1 field changed
      • changedInput schema / properties / workspace_id / description
        Previous 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."
    • Changedwell_wait_for_selection2 fields changed
      • changedInput schema / properties / workspace_id / description
        Previous 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."
      • addedOutput schema / properties / resolved_workspace
        Added 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"
        +}
  16. 9 tool updates
    • Addedwell_create_statement_upload
    • Addedwell_get_burn
    • Addedwell_get_cash_flow_bridge
    • Addedwell_get_cash_forecast
    • Changedwell_get_cash_position2 fields changed
      • addedInput schema / properties / month
        Added 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"
        +}
      • addedInput schema / properties / year
        Added value: +{
        +  "description": "Calendar year of the reporting period, e.g. 2026. Must be given together with `month`.",
        +  "maximum": 2100,
        +  "minimum": 2000,
        +  "type": "integer"
        +}
    • Changedwell_get_cost_structure2 fields changed
      • addedInput schema / properties / month
        Added 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"
        +}
      • addedInput schema / properties / year
        Added value: +{
        +  "description": "Calendar year of the reporting period, e.g. 2026. Must be given together with `month`.",
        +  "maximum": 2100,
        +  "minimum": 2000,
        +  "type": "integer"
        +}
    • Changedwell_get_runway4 fields changed
      • addedInput schema / properties / month
        Added 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"
        +}
      • addedInput schema / properties / year
        Added value: +{
        +  "description": "Calendar year of the reporting period, e.g. 2026. Must be given together with `month`.",
        +  "maximum": 2100,
        +  "minimum": 2000,
        +  "type": "integer"
        +}
      • addedOutput schema / properties / change
        Added value: +{
        +  "type": "number"
        +}
      • addedOutput schema / properties / trend
        Added value: +{
        +  "enum": [
        +    "up",
        +    "down",
        +    "neutral"
        +  ],
        +  "type": "string"
        +}
    • Changedwell_preview_invoice_fetch2 fields changed
      • addedOutput schema / properties / scoped_to_selected_counterparties
        Added 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"
        +}
      • addedOutput schema / properties / selection_scope
        Added 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"
        +}
    • Addedwell_upsert_accounting_settings
  17. 4 tool updates
    • Changedwell_get_runway1 field changed
      • addedOutput schema / properties / forecast
        Added 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"
        +}
    • Changedwell_list_connectors2 fields changed
      • addedInput schema / properties / from_selection
        Added 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"
        +}
      • addedOutput schema / properties / scope
        Added 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"
        +}
    • Changedwell_list_missing_invoices11 fields changed
      • changedOutput schema / properties / dropped_groups / properties / bank_internal / description
        Previous 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."
      • changedOutput schema / properties / dropped_groups / properties / unknown / description
        Previous 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."
      • changedOutput schema / properties / dropped_groups / properties / unnamed_company / description
        Previous 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."
      • changedOutput schema / properties / months / items / properties / dropped_groups / properties / bank_internal / description
        Previous 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."
      • changedOutput schema / properties / months / items / properties / dropped_groups / properties / unknown / description
        Previous 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."
      • changedOutput schema / properties / months / items / properties / dropped_groups / properties / unnamed_company / description
        Previous 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."
      • addedOutput schema / properties / rows / items / properties / available_modes
        Added 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"
        +}
      • changedOutput schema / properties / rows / items / properties / logo_url / description
        Previous 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."
      • addedOutput schema / properties / rows / items / properties / transactions
        Added 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"
        +}
      • addedOutput schema / properties / rows / items / properties / transactions_omitted
        Added 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"
        +}
      • changedOutput schema / properties / rows / items / required
        Previous 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"
        +]
    • Changedwell_preview_invoice_fetch19 fields changed
      • addedOutput schema / properties / agents / items / properties / connect_routed_counterparties
        Added 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"
        +}
      • changedOutput schema / properties / agents / items / properties / counterparties / description
        Previous 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."
      • addedOutput schema / properties / agents / items / properties / counterparties / items / properties / suggested_route
        Added 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"
        +}
      • changedOutput schema / properties / agents / items / properties / counterparties / items / required
        Previous 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"
        +]
      • addedOutput schema / properties / agents / items / properties / domain
        Added 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."
        +}
      • addedOutput schema / properties / agents / items / properties / tx_count / description
        Added value: +"Transactions this agent run would fetch, over every counterparty listed above."
      • addedOutput schema / properties / agents / items / properties / url
        Added 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."
        +}
      • changedOutput schema / properties / agents / items / required
        Previous 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"
        +]
      • addedOutput schema / properties / collect_url
        Added 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."
        +}
      • addedOutput schema / properties / collect_url_omits
        Added 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"
        +}
      • changedOutput schema / properties / counts / description
        Previous 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."
      • changedOutput schema / properties / counts / properties / agent_tx / description
        Previous value: -"Transactions those agents would cover."New value: +"Transactions those agent runs would fetch."
      • changedOutput schema / properties / counts / properties / agents / description
        Previous 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."
      • addedOutput schema / properties / counts / properties / connect / description
        Added value: +"Counterparties whose suggested route is connecting a service."
      • addedOutput schema / properties / counts / properties / upload / description
        Added value: +"Counterparties whose invoice only a manual upload can obtain."
      • changedOutput schema / properties / months / items / properties / counts / properties / agent_tx / description
        Previous value: -"Transactions those agents would cover."New value: +"Transactions those agent runs would fetch."
      • changedOutput schema / properties / months / items / properties / counts / properties / agents / description
        Previous 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."
      • addedOutput schema / properties / months / items / properties / counts / properties / connect / description
        Added value: +"Counterparties whose suggested route is connecting a service."
      • addedOutput schema / properties / months / items / properties / counts / properties / upload / description
        Added value: +"Counterparties whose invoice only a manual upload can obtain."
  18. 3 tool updates
    • Changedwell_list_workspaces2 fields changed
      • addedOutput schema / properties / session / properties / selected_counterparties
        Added 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."
        +}
      • changedOutput schema / properties / session / required
        Previous value: -[
        -  "pinned_workspace_id",
        -  "workspace_queue",
        -  "selected_periods"
        -]New value: +[
        +  "pinned_workspace_id",
        +  "workspace_queue",
        +  "selected_periods",
        +  "selected_counterparties"
        +]
    • Changedwell_switch_workspace7 fields changed
      • changedInput schema / properties / ack / description
        Previous 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."
      • addedInput schema / properties / counterparties
        Added 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"
        +}
      • addedInput schema / properties / counterparty_periods
        Added 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"
        +}
      • changedInput schema / properties / periods / description
        Previous 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."
      • changedInput schema / properties / workspace_ids / description
        Previous 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."
      • changedOutput schema / properties / changed / items / enum
        Previous value: -[
        -  "workspace",
        -  "periods",
        -  "connectors_ack",
        -  "bank_ack"
        -]New value: +[
        +  "workspace",
        +  "periods",
        +  "counterparties",
        +  "connectors_ack",
        +  "bank_ack"
        +]
      • addedOutput schema / properties / selected_counterparties
        Added 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"
        +}
    • Changedwell_wait_for_selection6 fields changed
      • changedInput schema / properties / kind / description
        Previous 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)."
      • changedInput schema / properties / kind / enum
        Previous value: -[
        -  "workspace",
        -  "periods",
        -  "connect_ack",
        -  "bank_ack"
        -]New value: +[
        +  "workspace",
        +  "periods",
        +  "counterparties",
        +  "connect_ack",
        +  "bank_ack"
        +]
      • changedOutput schema / properties / kind / enum
        Previous value: -[
        -  "workspace",
        -  "periods",
        -  "connect_ack",
        -  "bank_ack"
        -]New value: +[
        +  "workspace",
        +  "periods",
        +  "counterparties",
        +  "connect_ack",
        +  "bank_ack"
        +]
      • addedOutput schema / properties / selection / properties / counterparties
        Added 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"
        +}
      • addedOutput schema / properties / selection / properties / periods / description
        Added 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."
      • addedOutput schema / properties / selection / properties / workspace_id / description
        Added value: +"The pinned workspace on kind \"workspace\"; the workspace the picked counterparties belong to; the workspace the acknowledgement was made in."
  19. 2 tool updates
    • Changedwell_get_cost_structure2 fields changed
      • addedOutput schema / properties / period_end
        Added value: +{
        +  "description": "Inclusive YYYY-MM-DD end of that window.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / period_start
        Added value: +{
        +  "description": "Inclusive YYYY-MM-DD start of the window the amounts cover (the latest closed month).",
        +  "type": "string"
        +}
    • Addedwell_get_design_tokens

Related MCP Connectors

Related MCP Servers

  • A
    license
    B
    quality
    B
    maintenance
    Enables 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.
    10
    MIT
  • A
    license
    A
    quality
    D
    maintenance
    Enables Wave invoicing operations including listing invoices, retrieving details, and generating branded PDFs directly from AI assistants.
    5
    7 npm
    ISC
  • A
    license
    C
    quality
    C
    maintenance
    Enables AI assistants to manage invoices, contacts, purchases, journal entries, and other accounting operations via the Fiken API.
    100
    6 npm
    1
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    Connects AI assistants to QuickBooks Online, enabling management of invoices, customers, expenses, and reports through natural language.
    MIT
Try in Browser

Glama MCP Gateway

Add one secure layer between your agents and this server.

Resources