Skip to main content
Glama

Amber

面向 AI 助手的长期记忆。

Amber 是一个 MCP 服务器,为任何 AI 助手提供跨对话的持久化、可搜索记忆。你的 AI 会记住偏好、决策、项目上下文和个人细节——无需你做任何特殊操作。

正常对话即可。Amber 会存储重要内容,并在相关时找到它。

快速安装

一条命令。适用于任何兼容 MCP 的客户端。

Claude Code / Claude Desktop

claude mcp add --transport http --scope user amber https://mcp.ambermem.com

Cursor

添加到 ~/.cursor/mcp.json(Windows 上为 %USERPROFILE%\.cursor\mcp.json):

{
  "mcpServers": {
    "amber": {
      "url": "https://mcp.ambermem.com"
    }
  }
}

ChatGPT

设置 → 连接器 → 创建 → URL:https://mcp.ambermem.com

Windsurf

添加到 ~/.codeium/windsurf/mcp_config.json:

{
  "mcpServers": {
    "amber": {
      "serverUrl": "https://mcp.ambermem.com"
    }
  }
}

VS Code (GitHub Copilot)

添加到 .vscode/mcp.json:

{
  "servers": {
    "amber": {
      "type": "http",
      "url": "https://mcp.ambermem.com"
    }
  }
}

任何 MCP 客户端

URL:https://mcp.ambermem.com | 传输方式:Streamable HTTP | 认证:OAuth 2.1(自动发现)

Related MCP server: MCP-Mem0

工作原理

  1. 你正常与 AI 对话。 Amber 在后台存储重要事实。

  2. 下次对话时, 当上下文有帮助时,你的 AI 会自动搜索 Amber。

  3. 记忆随时间提升。 用得越多,效果越好。

无需配置。无需标签。无需手动整理。

Amber 的与众不同之处

特性

基础记忆服务器

Amber

存储

每条记忆一个 embedding

每个事实多个语义变体

搜索

单一向量查找

混合:向量 + 关键词 + RRF 融合

查询

仅精确匹配

自动扩展(同义词、改写)

输入

原样存储

LLM 分块为原子事实

主题

手动标签或无

LLM 自动分类

时间

无时间感知

自然语言时间解析("上周"、"3 天前")

技术细节

  • 23 个 MCP 工具(14 个记忆、7 个账户、2 个反馈/通知)

  • 混合检索管道:向量搜索 + 全文搜索 + 倒数排名融合(Reciprocal Rank Fusion)

  • LLM 驱动的分块:文本 → 原子事实,每个都可独立搜索

  • 多变体嵌入:每个事实存储约 4 个改写版本以提高召回率

  • 查询扩展:搜索自动改写以查找语义相关的记忆

  • 自动主题分类:记忆按 LLM 生成的主题分组

  • 时间解析:"我上周说了什么?"直接可用

  • 异步处理:存储在后台 10-30 秒内完成,绝不阻塞你的对话

定价

  • 60 天免费试用——不收费,随时取消

  • 试用后每月 $2.99,通过 PayPal 支付

  • 即时取消——让 AI 帮你取消,或直接通过 PayPal 取消

  • 无锁定——随时以 JSON 格式导出所有数据

隐私

  • 不收集邮箱

  • 无营销,无垃圾邮件

  • 每个用户数据隔离(独立数据库)

  • PayPal 处理所有支付信息

  • 支持完整导出 + 账户删除

  • 符合 GDPR(设计上实现数据最小化)

架构

Amber 运行在 Cloudflare Workers 上(零冷启动,全球边缘部署),使用 Turso 数据库(每个用户一个,完全隔离)。LLM 处理使用 Gemini Flash 进行分块/扩展,使用 OpenAI 进行嵌入。

完整技术文档:ambermem.com/llms.txt

链接

常见问题

会拖慢我的 AI 吗? 不会。存储是异步的(后台进行)。搜索增加不到 1 秒。

如果 Amber 关闭了怎么办? 随时以 JSON 格式导出所有数据。你的数据永远属于你。

我需要 PayPal 账户吗? 目前需要。PayPal 同时处理身份验证和计费。更多登录方式即将推出。

我的数据安全吗? 每个用户都有完全隔离的数据库。用户之间不共享任何数据。Amber 无法访问你的 PayPal 支付详情。

我可以自托管吗? 目前不可以。Amber 是托管服务。我们负责基础设施、扩展和 LLM 成本,你无需操心。

Available Tools

29 tools
amber_cancel_account_deletionCancel scheduled account deletionA
Idempotent
Inspect

Cancel a previously scheduled account deletion. Removes the deletion deadline and unblocks memory tools immediately. Access resumes until next_billing_date (the remaining paid period). No data is lost.

Side effects: clears deletion_scheduled_at from the account. The PayPal subscription was already cancelled when deletion was scheduled -- call amber_reactivate_subscription to start a new one if the paid period has expired. Returns code: not_scheduled if no deletion is pending. Not rate-limited.

NameRequiredDescriptionDefault

No parameters

Output Schema

NameRequiredDescription
statusYesAlways `cancelled`: the scheduled deletion is off and the account is fully intact. Nothing had been destroyed while it was pending.
messageYesA human-readable confirmation to relay to the user.

TDQS

A4.7/5.0
Behavior5/5

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

Annotations only give the generic safety profile (readOnly=false, idempotent=true, destructive=false); the description goes far beyond that with concrete effects: unblocking memory tools immediately, access only until next_billing_date, no data loss, clearing deletion_scheduled_at, and the prior cancellation of the PayPal subscription. It even discloses the 'not_scheduled' error code and confirms no rate limiting, which 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.

Conciseness5/5

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

The outcome and immediate effect are front-loaded, followed by a clearly labeled 'Side effects' block and an error-case sentence. Every sentence carries actionable information, including the subscription-reactivation pointer and the error code, with no filler.

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

Completeness5/5

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

For a zero-argument state-changing tool with an output schema already covering return values, the description supplies the missing pieces an agent needs: what state is cleared, what access resumes, what the billing consequence is, and the failure mode. Nothing material is left to inference.

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

Parameters4/5

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

The tool takes zero parameters, so there is nothing for the description to disambiguate; the baseline for a 0-param tool is a 4. The description correctly avoids inventing parameter talk and instead spends its words on behavior.

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 names a specific verb and resource ('Cancel a previously scheduled account deletion'), which cleanly distinguishes it from siblings like amber_delete_account (irreversible) and amber_cancel_subscription (billing only). An agent can route between these without opening any schema.

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

Usage Guidelines4/5

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

The description establishes the precondition (a deletion must be pending) and the alternative path when the paid period has expired ('call amber_reactivate_subscription to start a new one'). It does not explicitly say when not to use it versus amber_delete_account, but the conditional guidance is clear enough for confident selection.

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

amber_cancel_subscriptionCancel the subscriptionA
DestructiveIdempotent
Inspect

Cancel the user's PayPal subscription. Access continues until next_billing_date (already paid). Data is preserved, not deleted. Do NOT use if the user wants to delete their account entirely -- use amber_delete_account instead.

Cancellation is sent to PayPal immediately; status update arrives via webhook within ~1 minute. Returns code: no_subscription if none exists. Requires confirm: true.

Errors: cancelled (you did not pass confirm: true; NOTHING was attempted - the name is historical and collides with the success status: cancelled this same tool returns when a cancellation DOES happen, so branch on which FIELD carried it: error.code means you did not confirm, status means it is done), no_subscription (this account has no subscription), not_found (PayPal did not recognise the subscription id on this account; it may already have been cancelled or expired), deletion_scheduled (the account is already scheduled for deletion, which ends billing anyway), cancel_failed (PayPal refused; the subscription is unchanged, so it is safe to retry).

NameRequiredDescriptionDefault
confirmYesWhen `true`: cancels the subscription on PayPal and updates account status. When `false`: returns `code: cancelled` with no side effects, letting you confirm intent first. A STRINGIFIED boolean is also accepted (`"true"`, `"TRUE"`, `" true "`), because some clients JSON-stringify every argument and would otherwise be unable to cancel at all; the published type is `boolean` because that is what a client should send.

Output Schema

NameRequiredDescription
statusYesAlways `cancelled`. Access continues to the end of the paid period rather than stopping now, and memories are preserved either way.
messageYesA human-readable summary, including when access actually ends.

TDQS

A4.8/5.0
Behavior5/5

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

Goes well beyond the annotations: access continues until next_billing_date, data is preserved rather than deleted, the request is dispatched to PayPal immediately with a webhook-driven status update in ~1 minute, and each failure mode is enumerated with its consequence ('NOTHING was attempted', 'the subscription is unchanged, so it is safe to retry'). The explicit error.code-vs-status disambiguation for the colliding 'cancelled' name is a genuine behavioral disclosure that no annotation carries. The 'data is preserved' line is a useful clarification of destructiveHint=true rather than a 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?

Front-loaded with purpose and the exclusion, then delivery timing, then errors — a sensible ordering with no filler sentences. The error enumeration is dense and the 'cancelled' collision explanation is a long parenthetical that takes effort to parse, but every clause in it carries operational 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?

Covers purpose, exclusion, prerequisite, timing, data-retention consequence, and the full set of documented failure modes with recovery advice, which is everything an agent needs for a destructive, open-world mutation. Return-value detail is rightly omitted since an output schema exists.

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 single parameter's description already explains the true/false split, so the baseline is 3; the description still adds value by restating the confirm: true requirement and, more importantly, by mapping the confirm: false outcome to a specific error field so the agent does not mistake it for a completed cancellation. It does not repeat the stringified-boolean acceptance, but that is already fully documented in the schema.

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

Purpose5/5

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

States a specific verb and resource ('Cancel the user's PayPal subscription') and immediately bounds the scope by naming the sibling it is not ('Do NOT use if the user wants to delete their account entirely -- use amber_delete_account instead'). An agent can separate this from amber_manage_subscription, amber_reactivate_subscription and amber_delete_account without reading any schema.

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

Usage Guidelines5/5

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

Gives explicit when-to-use, when-not-to-use, and the named alternative for the excluded case. It also states the confirm: true prerequisite and warns that confirm: false is a harmless intent check, which is exactly the routing information an agent needs before calling a cancel tool.

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

amber_create_backup_keyCreate a backup keyA
Destructive
Inspect

Create a key that lets an automated job download a full export of this account without an AI session -- for example a weekly cron that saves everything to the user's own S3 bucket or NAS.

Usage: a backup is a task. POST /backup with header X-Amber-Backup-Key: <key> returns a task_id; poll GET /backup/<task_id> with the same header until status is complete, then download the download_url it carries. A ready-to-run weekly script is at https://ambermem.com/backups

IMPORTANT: the key is shown ONCE and never again -- only a hash is stored, so it cannot be recovered, only replaced. RELAY IT TO THE USER VERBATIM and tell them to save it somewhere safe. There is one key per account: calling this again REPLACES the previous key, which immediately stops working. That is how rotation works, and how to respond if a key leaks. The response says when the replaced key was last used, so you can tell the user whether a scheduled job just stopped -- RELAY THAT WARNING if it is there, because the job will not report the failure itself. The key does exactly TWO things: download an export, and start a restore -- which is a WRITE, so treat it as more than a read credential. It cannot read an individual memory, change the subscription, or delete the account. Corrected 2026-09-19: this said "exactly one thing ... cannot read, write or delete anything", which understated it, and the tool's own response text said the opposite. A restore also puts trashed memories back AS trash, which is a change rather than an addition. The backup includes deleted memories still in the trash. Backups are limited to one per hour. Revoke with amber_revoke_backup_key. Not rate-limited. ASK THE USER how long a gap without a successful backup should be treated as a problem, and pass it as warn_after_days. Without it Amber has to fall back to a cautious 30 days, and a backup that stopped is invisible until the day it is needed.

NameRequiredDescriptionDefault
warn_after_daysNoWARN THE USER WHEN NO BACKUP HAS SUCCEEDED FOR THIS MANY DAYS. It is the alert threshold itself, in days, used exactly as given: pass 10 and the warning appears once nothing has succeeded for 10 days. Nothing is multiplied or padded. WHY IT EXISTS. A backup that stops does not announce itself. The job receives an error and writes it to a log nobody reads, so the user goes on believing they have backups and finds out on the day they need one, which is the only day it cannot be fixed. Amber can see WHEN a backup last succeeded and nothing else. It cannot know when the next one is due, whether the machine is meant to be on, or whether the user is on holiday. So the line between 'quiet' and 'broken' has to come from them. HOW TO CHOOSE IT. Ask how often the job runs, then allow room for a missed run or two, and pass the RESULT. A nightly backup is comfortable at 3. The weekly script at the documented backup script is comfortable at 14 to 21. A monthly job wants 45 or more. Err high rather than low: a warning that fires on a healthy setup teaches the user to ignore the channel, and then the real one is ignored too. DO NOT pass the backup interval itself. `warn_after_days: 7` for a weekly job means a single missed run raises a warning, which for most people is a machine that was off for a night. WHEN AND WHERE IT FIRES. In the user's tool responses, at most once a day, and it clears itself the moment a backup succeeds again. There is nowhere else it can go: Amber holds no email address for anybody, by design. Relay it when you see it, because it is addressed to the user and they will not see it otherwise. IT IS NOT THE ONLY CHECK. If something presents a backup key that was replaced or revoked, the user is told on their next message regardless of this number, and told what to fix. This threshold is the net that catches everything else: a dead machine, a disabled schedule, a full disk, an edited script. IF YOU LEAVE IT OUT. Nothing breaks. An existing value is KEPT, so rotating a key does not silently widen the window. With no value at all Amber warns after 30 days. Prefer asking: for a nightly backup the default is a month of loss before anybody says anything. CHANGING IT LATER means calling this tool again, which also REPLACES the key and forces the user to update their job. Ask now rather than revisiting it.

Output Schema

NameRequiredDescription
messageYesA human-readable summary, including the replacement warning when there is one.
backup_keyYesThe key itself, shown ONCE and never recoverable. Relay it to the user verbatim and tell them to save it.
example_commandYesA ready-to-run curl that starts a backup with this key. Safe to show.
warn_after_daysYesThe backup-silence threshold now in force, in days: you are warned when no backup has succeeded for this many days. Echoed because creating a key is the only way to SET it, and a rotation that omits it deliberately carries the previous value forward -- so this is where you find out what you are on. Null means the server default applies.
replaced_existingYesTrue if this call revoked a previous key. Anything still using that key stops working immediately.
replaced_key_last_used_atYesWhen the key this call just revoked was last used successfully, or null if it was never used (or there was none). NOT trivia: a recent timestamp means a scheduled job was relying on it and has just stopped, and that job will not report the failure itself. WARN THE USER when this is set.

TDQS

A4.7/5.0
Behavior5/5

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

Annotations already declare destructiveHint=true and idempotentHint=false, but the description adds materially more: the key is shown once and only a hash is stored, calling again silently replaces the previous key and kills any running job, the response reports when the replaced key was last used, and the key's true blast radius is exactly two capabilities (download export, start restore — the latter being a write). It also discloses that trashed memories are included in the backup and that a restore re-creates them as trash.

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

Conciseness3/5

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

Front-loaded and well-structured with IMPORTANT and Usage markers, but it carries a meta-paragraph about a documentation correction ('Corrected 2026-09-19: this said ... which understated it') that helps no agent select or invoke the tool and reads as changelog noise. The operational content is dense but the correction digression and the duplicated 'relay it to the user' instruction cost 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 single-optional-parameter tool with an output schema, the description covers everything an agent needs: what it creates, the one-shot secret handling, rotation semantics, revocation path, rate limit, credential scope, and the warning behavior. Return-value details are correctly delegated to the output schema while the agent is still told to relay the secret and any replaced-key warning.

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 description for warn_after_days is already exhaustive, so the baseline is 3. The description still earns credit by adding an actionable directive the schema buries: 'ASK THE USER how long a gap without a successful backup should be treated as a problem,' plus the consequence of omission (30-day default, invisible failure). It does not restate the schema's interval-selection heuristics, which is appropriate.

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

Purpose5/5

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

States a specific verb and resource ('Create a key that lets an automated job download a full export of this account without an AI session') and immediately distinguishes it from amber_export_memories by scoping it to unattended/cron use. An agent can tell this apart from the sibling export and revoke tools without opening any schema.

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

Usage Guidelines5/5

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

Gives explicit when-to-use context (a weekly cron saving to S3/NAS), an end-to-end usage recipe (POST /backup with X-Amber-Backup-Key, poll GET /backup/<task_id>, then fetch download_url), names the revocation sibling (amber_revoke_backup_key), and states the one-per-hour limit. It also tells the agent to ask the user about the warning threshold before calling, which is guidance rather than mere description.

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

amber_delete_accountSchedule account deletionA
DestructiveIdempotent
Inspect

Schedule permanent account deletion in 30 days. Memory tools are blocked immediately (unlike cancellation, which keeps access until period end). Cancels the PayPal subscription. Reversible within 30 days via amber_cancel_account_deletion.

During the grace period: account management tools (status, export, feedback) remain accessible; memory tools are blocked. After 30 days: all data is permanently deleted.

Requires confirm: true. Returns code: cancelled when false, code: already_scheduled if already pending.

NameRequiredDescriptionDefault
confirmYesSet `true` to proceed, `false` to abort without changes. A STRINGIFIED boolean is also accepted (`"true"` and friends), because some clients JSON-stringify every argument; the published type is `boolean` because that is what a client should send.

Output Schema

NameRequiredDescription
statusYesAlways `scheduled`: nothing is deleted at the moment of this call.
messageYesA human-readable summary. Relay it, including the date and the fact that it is reversible until then.
deletion_dateYesWhen the data will actually be destroyed (ISO 8601). Until then `amber_cancel_account_deletion` undoes this completely.

TDQS

A4.8/5.0
Behavior5/5

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

The annotations already declare destructiveHint=true and idempotentHint=true, but the description goes well beyond: 30-day deferral, immediate memory-tool blocking versus continued access to account-management tools, automatic PayPal subscription cancellation, and the specific `already_scheduled` state on repeat calls. That is exactly the behavioral context annotations cannot express (timing, side effects on adjacent subsystems, reversibility window). Nothing here contradicts the destructive/idempotent 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?

Front-loaded with the core action and timeframe, then consequences, then requirements, then return codes — a sensible ordering. Slightly redundant: memory tools being 'blocked immediately' is stated in the first line and restated in the grace-period paragraph, which could be merged.

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, single-parameter, open-world tool this covers everything an agent needs: irreversible-after-30-days warning, what breaks immediately, what still works, the reversal tool, and expected response codes. An output schema exists, so not enumerating return fields further is fine.

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 `confirm` description already covers the boolean and the stringified-boolean edge case, so the description is not required to explain the parameter. It still adds value the schema does not: the concrete return codes `cancelled` (confirm=false) and `already_scheduled`, which tell the agent how to interpret the response rather than the input.

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 — 'Schedule permanent account deletion in 30 days' — and immediately distinguishes it from subscription cancellation ('unlike cancellation, which keeps access until period end') and from its own inverse tool `amber_cancel_account_deletion`. An agent can route correctly without opening any schema.

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

Usage Guidelines5/5

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

Explicitly separates this from the two nearest alternatives: subscription cancellation (which preserves access to period end) and `amber_cancel_account_deletion` (the reversal path). It also states the exact precondition (`confirm: true`) and what happens when it is false, so the when/when-not decision is fully determined.

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

amber_delete_memoryDelete memories (soft-delete)A
DestructiveIdempotent
Inspect

Soft-delete one or more memories to the trash. Reversible via amber_restore_memory. Do NOT use this for account-level cleanup -- use amber_delete_account instead.

Provide memory_id (single) or memory_ids (batch, max 100). At least one is required. Idempotent: already-deleted memories are skipped. EVERY ID you send is accounted for: deleted_count plus already_deleted_ids (existed, already trashed) plus not_found_ids (no such row). ALWAYS check deleted_count against how many IDs you sent -- a batch is NOT all-or-nothing, and a shortfall usually means those memories are still being written (amber_store_memory is async), so check amber_get_store_task_status and retry rather than assuming they are gone. If the IDs are already in the trash you get deleted_count: 0 with an explanatory message (a no-op, NOT an error); code: not_found is returned only when no memory exists with those IDs -- so a no-op is distinguishable from wrong IDs. VERIFY WHAT YOU DELETED: the reply carries deleted -- the full content of every memory this call actually trashed. READ IT and confirm it is what the user meant, because an id proves nothing: if you deleted the wrong memory this is the moment to notice, and amber_restore_memory puts it straight back. Tell the user what was deleted rather than only how many. Requires an active subscription. Not rate-limited.

Errors: invalid_params (no id supplied, or both singular and plural given). A wrong TYPE -- a number where a string belongs -- is caught a layer earlier and answers invalid_input. Deleting an id that does not exist is NOT an error: deleted reports what was actually trashed, so compare its length against how many ids you sent.

NameRequiredDescriptionDefault
memory_idNoUUID of a single memory to trash. Omit if using `memory_ids` for batch.
memory_idsNoArray of memory UUIDs to trash in one call (max 100). Omit if using `memory_id` for single.

Output Schema

NameRequiredDescription
statusYesAlways `deleted`. The memories moved to the TRASH and stay restorable for 30 days; nothing is destroyed by this call.
deletedYesThe memories this call actually trashed, in full. CHECK THESE: a count cannot tell you whether the right memory went, and this is the last moment the content is easy to see. Empty when nothing was deleted -- never absent, so there is one shape to handle.
deleted_countYesHow many memories this call actually moved to the trash. Compare against the number of IDs you sent: any shortfall is itemised in already_deleted_ids / not_found_ids.
not_found_idsNoIDs that matched NO memory. Present only when some did not resolve. Most often these are from a store that has not finished writing yet (amber_store_memory is async) rather than wrong IDs -- check amber_get_store_task_status and retry.
already_deleted_idsNoIDs that exist but were ALREADY in the trash, so this call was a no-op for them. Present only when some were.

TDQS

A4.5/5.0
Behavior4/5

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

Annotations already declare destructive=true and idempotentHint=true, and the description adds real value beyond that: reversibility, skip-already-deleted semantics, the no-op vs not_found distinction, the async-write race that causes batch shortfalls, subscription requirement, and no rate limit. Minor deduction because the error/verification material is stated twice in slightly different words, diluting rather than contradicting the disclosure.

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?

Core purpose and the sibling exclusion are front-loaded, but the text is dense and repetitive: the deleted_count-vs-sent check appears in the main body and again in the errors section, and the no-op vs not_found explanation is restated. Several clauses could be tightened without losing 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 destructive batch mutation this covers everything an agent needs: required argument cardinality, idempotency, partial-success accounting, error codes, the async-write caveat, and post-delete verification against the returned content. Output schema exists but the description still adds interpretive guidance on how to use those fields rather than merely repeating them.

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 each parameter, so the baseline is 3. The description earns above baseline by stating constraints the schema does not encode: at least one id is required (required array is empty) and supplying both singular and plural yields invalid_params. It does not restate the max-100 limit, but that is already in the schema.

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

Purpose5/5

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

States a specific verb and resource ("Soft-delete one or more memories to the trash") and immediately names the two siblings it must be distinguished from: amber_restore_memory for reversal and amber_delete_account as the wrong tool for account-level cleanup. An agent can route to it without opening any schema.

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

Usage Guidelines5/5

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

Explicit when-not guidance ("Do NOT use this for account-level cleanup -- use amber_delete_account instead") plus the alternative for undoing the action. It also prescribes the correct downstream flow after a shortfall (check amber_get_store_task_status and retry), covering the ambiguity an agent would otherwise guess at.

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

amber_export_memoriesStart a full export of all memoriesA
Read-only
Inspect

Start exporting your memories to a downloadable JSON file. Returns a task_id IMMEDIATELY -- the file is built in the background. Poll amber_get_export_status with that id until status is complete, then give the user the download_url it returns.

By DEFAULT exports ACTIVE memories only; pass include_deleted=true to also include trashed (soft-deleted) memories. IF THE USER ASKED FOR A BACKUP, PASS include_deleted=true. Amber's deletes are reversible, so an active-only archive is missing everything they could still restore -- on a real account that was 121 records against 6,334. The unattended route at /backups always includes the trash for that reason; active-only is the right choice for "let me look at my data", not for a backup. Each record carries content, subjects, topics, metadata (internal prefixes stripped), event dates, and timestamps (deleted records also carry deleted_at).

Use this when the user wants a backup, wants to migrate data, or asks "can I download my data?". Unlike amber_list_memories (which paginates one page at a time), this exports ALL memories into one file; unlike amber_search_memories (which finds specific content), this is a complete dump.

WHY IT IS ASYNC: an export of a large account cannot be produced inside one request, so it is built in slices in the background. There is no size limit and no truncation. A small account is usually ready on the first poll; a large one takes several. For an UNATTENDED schedule (a weekly cron with no assistant running), point the user at https://ambermem.com/backups instead -- same mechanism over plain HTTP.

Does not modify any data -- read-only. Requires an active subscription. Not rate-limited.

Errors: queue_failed (the export could not be queued; nothing was started, so retrying is safe and creates no orphan task).

NameRequiredDescriptionDefault
include_deletedNoAlso include trashed (soft-deleted) memories. Default false -- active memories only.

Output Schema

NameRequiredDescription
statusYesAlways `pending`: the export was queued. There is no download link yet, so do not tell the user it is ready. Poll `amber_get_export_status`.
messageYesA human-readable summary of what was started and what to poll.
task_idYesPass this to `amber_get_export_status`. Do NOT tell the user the export is ready yet -- nothing has been written at this point.
exported_atYesThe timestamp stamped into the file itself, fixed when the task started.

TDQS

A4.9/5.0
Behavior5/5

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

Annotations only declare a safe read; the description adds the async contract (task_id returned before the file exists, poll amber_get_export_status), the no-truncation guarantee, subscription requirement, absence of rate limiting, record contents, and the queue_failed semantics with safe-retry guidance. That is substantial context the 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.

Conciseness4/5

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

Paragraphs are grouped by concern (mechanics, the include_deleted decision, alternatives, async rationale, constraints) with the most important mechanics front-loaded. It runs long and the backup/include_deleted point is restated two or three times, plus "read-only" repeats the annotation, but nothing is genuinely off-topic.

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 an output schema existing, the description still supplies the orchestration loop (task_id → poll → download_url) and the one documented failure mode, which is what an agent actually needs to complete the workflow. Nothing material is missing for a single-optional-parameter async export.

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%, yet the description adds real decision meaning for include_deleted: why backups must set it true, the reversibility rationale, and a concrete 121-vs-6,334 record example. It tells the agent how to choose the value, not just what it does.

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?

"Start exporting your memories to a downloadable JSON file" gives a specific verb and resource, and the description explicitly separates it from amber_list_memories (paginated) and amber_search_memories (targeted lookup). An agent can pick it out of a 29-tool sibling set 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?

It names the triggering intents (backup, migration, "can I download my data?"), names the alternatives and the condition that rules them out, and adds the unattended-cron route via /backups. Both when-to-use and when-not-to-use are covered.

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

amber_get_account_statusGet account statusA
Read-onlyIdempotent
Inspect

Return the authenticated user's account summary: subscription state, next billing date, memory counts, and account creation date. Use this when the user asks about their account, subscription status, or how many memories they have.

After cancellation, access continues until next_billing_date (the end of the paid period). Once ACCESS ends, memory counts show as null (data is preserved, just not queryable until resubscribed) -- note that is later than subscription_active turning false, because a cancelled subscription keeps access until its paid period ends and reports real counts until then. If deletion_scheduled_at is set, the account is pending permanent deletion -- use amber_cancel_account_deletion to stop it. Read-only -- does not modify any account state. Not rate-limited.

NameRequiredDescriptionDefault

No parameters

Output Schema

NameRequiredDescription
user_idYesAmber's own id for this account. Stable, and safe to show.
payer_idYesPayPal Payer ID (permanent account identifier).
member_sinceYesWhen the account was created, as an ISO 8601 UTC datetime, not when the current subscription started.
memory_countYesActive memory count. Null once ACCESS has ended, which is not the same as `subscription_active: false`: a cancelled subscription keeps access until its paid period ends, and reports real counts throughout. Data is preserved either way, just not queryable once access ends.
subscription_idYesThe PayPal subscription this account bills through, or null if there has never been one.
migration_lockedYesTrue while Amber is moving this account's database, during which WRITES are refused with `migration_in_progress` and reads keep working. Normally false. If a store was refused, this is how a caller confirms why and that retrying shortly will succeed rather than the request being malformed.
next_billing_dateYesDate when access expires (ISO 8601). When subscription_active is false and this date is in the future, the user still has access until this date but will NOT be charged again (cancelled/no subscription). When subscription_active is true, this is the next billing date. Null if unknown.
subscription_activeYesWhether the account can currently store and search. False does NOT mean data was lost: memories are preserved and become queryable again on resubscribing.
deleted_memory_countYesTrashed memory count, on the same rule as `memory_count`: null once access has ended, not merely when `subscription_active` is false.
deletion_scheduled_atYesISO 8601 date when the account will be permanently deleted, or null if not scheduled. Use `amber_cancel_account_deletion` to cancel.

TDQS

A4.6/5.0
Behavior5/5

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

Annotations already declare readOnly/idempotent/non-destructive, but the description adds genuinely non-inferable behavior: no rate limiting, memory counts read as null after access lapses while data is preserved, access persists past `subscription_active` until `next_billing_date`, and the meaning of `deletion_scheduled_at`. This is exactly the operational 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?

Front-loaded with purpose and trigger before the edge-case detail, and the closing 'Read-only... Not rate-limited' tags are efficient. The middle sentence is convoluted and partly restates itself ('note that is later than `subscription_active` turning false, because a cancelled subscription keeps access until its paid period ends and reports real counts until then').

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

Completeness5/5

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

For a zero-param read with an output schema and full annotation coverage, the description covers everything an agent needs: what triggers it, what the returned states mean over the subscription lifecycle, and where to go to reverse a pending deletion. No meaningful gap remains.

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

Parameters4/5

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

Zero parameters, so the baseline is 4. The description instead spends its words on return-field semantics (what `next_billing_date` and null memory counts mean), which is useful even though an output schema exists.

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 ('Return the authenticated user's account summary') and enumerates exactly what comes back: subscription state, next billing date, memory counts, creation date. An agent can immediately distinguish this read-only status snapshot from the sibling mutation tools like amber_manage_subscription or amber_cancel_account_deletion.

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 trigger ('Use this when the user asks about their account, subscription status, or how many memories they have') and routes one adjacent case away with 'use amber_cancel_account_deletion to stop it'. It does not, however, disambiguate against amber_manage_subscription or amber_cancel_subscription for subscription-related questions, so the routing is partial rather than exhaustive.

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

amber_get_export_statusCheck export status and get the download linkA
Read-onlyIdempotent
Inspect

Check an export started by amber_export_memories, and collect its download link once it is finished. Not rate-limited. Read-only. Requires an active subscription.

Statuses: pending (queued), running (writing the file), complete (ready -- download_url is set), failed (gave up; error says why).

Poll every few seconds. memory_count climbs while it runs, so a caller can tell progress from a stall. Only report the export as ready to the user once status is complete.

The download_url is signed and valid for 7 days, matching how long the export itself is kept, so an interrupted download can be RESUMED with a Range request rather than restarted. Call this again for a fresh link if it has expired, and note an old link. The export itself is kept for 7 days, after which this returns code: not_found.

NameRequiredDescriptionDefault
task_idYesThe id returned by `amber_export_memories`. An unknown or expired id returns `code: not_found`.

Output Schema

NameRequiredDescription
errorYesWhy the export failed. Null unless status is `failed`.
statusYes`pending` queued, `running` writing, `complete` ready to download, `failed` stopped (see `error`). A stalled export becomes `failed` with a reason rather than sitting on `running`.
task_idYesThe task this report is about.
chunk_countYesHow many background passes have written a piece of the file. Progress, and the reason a large export takes more than one poll.
exported_atYesThe moment the export represents. Every memory in the file is as it was at this instant, whatever the export took to finish.
download_urlYesSigned download link, set only when status is `complete`. Short-lived: poll again for a fresh one rather than reusing it.
memory_countYesMemories written so far. This is the FINAL count once status is complete.
include_deletedYesWhether this archive carries TRASHED memories as well as active ones. Stated rather than inferred, because the count alone cannot tell a full backup from a partial one: `POST /backup` (the unattended route) always includes the trash, while `amber_export_memories` includes it only when asked. If the user wanted a BACKUP, this should be true.

TDQS

A4.4/5.0
Behavior4/5

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

Goes well beyond the annotations: states it is not rate-limited, requires an active subscription, and documents the status semantics, progress signal (`memory_count` climbing), 7-day retention and post-expiry `code: not_found`. The resumable-Range detail is genuinely useful. Minor gap: no note on whether polling itself incurs costs or how long a `pending` task may sit before it counts as stalled.

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

Conciseness4/5

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

Front-loaded with purpose and usage, then statuses, then link lifetime — a sensible order. Slightly redundant: the 7-day export retention is stated twice ('valid for 7 days, matching how long the export itself is kept' and later 'The export itself is kept for 7 days').

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?

An output schema exists, so return values needn't be spelled out, yet the description still covers statuses, error signalling and link expiry. For a one-parameter polling tool this is complete; 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 the schema already explains that `task_id` is the id returned by `amber_export_memories` and that unknown/expired ids yield `not_found`. The description repeats that provenance rather than adding new 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?

States a specific verb+resource ('Check an export started by `amber_export_memories`') and adds the second function (collect the download link). It names the sibling that creates the export, so the agent can tell it apart from the import-status and store-task-status 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?

Explicit operating instructions: poll every few seconds, only report ready once status is `complete`, and call again for a fresh link if the URL expired. The lifecycle (pending/running/complete/failed) gives the agent the decision rules it needs.

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

amber_get_import_statusCheck a restore in progressA
Read-onlyIdempotent
Inspect

Check a restore in progress, whichever way it was started: the upload panel from amber_upload_backup, the browser page at https://ambermem.com/restore, a URL given to amber_import_memories, or a presigned upload from POST /import/presign. Not rate-limited. Read-only. Requires an active subscription.

Statuses: awaiting_upload (a link was handed out and no file has arrived yet; not a failure), pending (queued), running (restoring), complete, failed (error says why).

imported_count climbs while it runs, so a caller can tell progress from a stall. There is NO total to compare it against: the archive is never read in full up front, which is what lets a restore of any size cost the same per step. It counts memories PROCESSED, so re-running a restore counts them again even though nothing is written twice.

IF IT FAILED, SAY SO PLAINLY AND RELAY error, which names the cause and what to do. Starting a new restore is safe: memories keep their ids, so anything already restored is skipped rather than written twice. If the error says the file is damaged or cut short, the same file will stop at the same place, so restore from a fresh backup instead. A memory that was in the trash in the backup is restored INTO the trash, and is counted in imported_count like the rest. Errors: not_found (no task with that id, or it belongs to another account, or it aged out after 7 days), invalid_input (the id is missing or not a string -- the schema layer answers this one).

If the user still needs to get a file INTO Amber, amber_upload_backup shows them an uploader; https://ambermem.com/backups documents the scripted route.

NameRequiredDescriptionDefault
task_idYesThe id returned by whichever call started the restore: `amber_upload_backup` with action `finish`, `amber_import_memories`, or `POST /import/presign`. An unknown or expired id returns `code: not_found`.

Output Schema

NameRequiredDescription
errorYesWhy it stopped, when status is `failed`. Re-uploading the SAME file is safe and resumes rather than duplicating, so say that when you relay this.
statusYes`awaiting_upload` set up and waiting for the file, which is the normal state for the whole upload flow and NOT an error, `pending` queued, `running` restoring, `complete` finished, `failed` stopped (see `error`). A restore never sits on `running` for ever: a stall becomes `failed` with a reason.
task_idYesThe task this report is about.
exported_atYesWhen the archive was originally taken.
imported_countYesMemories PROCESSED so far, and the final count once status is `complete`. Not a fraction: the archive is never read in full up front, so nothing knows the total until the restore finishes. Not a count of new rows either: writes are keyed on the id the memory had in the archive, so restoring a file twice counts every memory again while writing none of them twice.

TDQS

A4.8/5.0
Behavior5/5

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

Annotations already declare readOnly/idempotent/non-destructive, and the description still adds real behavioral context: not rate-limited, requires an active subscription, full status lifecycle, 7-day task expiry, and the `not_found`/`invalid_input` error surface. It also explains that `imported_count` counts processed memories with no total available, which prevents an agent from misreading a stall.

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

Conciseness4/5

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

Long but front-loaded and organized into purpose, status vocabulary, progress semantics, and failure handling, with no filler sentences. The closing note on `amber_upload_backup` is slightly tangential for a status tool but earns its place as a next-step pointer.

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?

An output schema exists, so return values need not be re-specified, yet the description still defines every status value and the meaning of `imported_count`, plus the trash-restore edge case. For a single-parameter polling tool this leaves nothing an agent needs unresolved.

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 `task_id` is already documented, but the description adds meaning the schema does not: it enumerates every call that can produce the id and explains that a mismatched or aged-out id surfaces as `not_found`. That is useful routing context rather than a restatement.

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 ('Check a restore in progress') and immediately scopes it to every way a restore can be started, which separates it from the starter tools (`amber_upload_backup`, `amber_import_memories`) and from the export-status sibling. An agent can tell this is the polling endpoint for imports without opening the schema.

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

Usage Guidelines5/5

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

Explicitly covers when to use it (polling a restore regardless of origin), what to do on failure (relay `error`, restart safely, use a fresh backup if the file is damaged), and routes the agent to `amber_upload_backup` when the user still needs to get a file in. It even pre-empts a wrong inference by saying `awaiting_upload` is not a failure.

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

amber_get_memoryGet a memory by IDA
Read-onlyIdempotent
Inspect

Retrieve a single memory by its ID with full, untruncated content. Use this after amber_search_memories when a result's content was truncated (indicated by truncated: true), or when you have a memory_id from a previous interaction and need the complete text.

Returns the memory with its full content, metadata (if any), topic names (if any), and created_at timestamp. Resolves a memory by id whether it is active OR in the trash -- a non-empty deleted_at marks a trashed one (restore it with amber_restore_memory). Returns code: not_found only if no memory with that id exists at all. Read-only -- does not modify the memory. Not rate-limited. Requires an active subscription.

NameRequiredDescriptionDefault
memory_idYesUUID of the target memory. Found in `amber_store_memory` task results, `amber_search_memories` result items, or `amber_list_memories` pages.

Output Schema

NameRequiredDescription
memoryYesThe memory: full content, its `subject` (a single string, not a list), metadata, topic names, and timestamps. It does NOT carry the stored variants -- those are search-time phrasings, not part of the record. A memory in the trash is returned too, with its deletion time set.

TDQS

A4.5/5.0
Behavior4/5

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

Annotations already declare readOnlyHint/idempotentHint/destructiveHint:false, but the description goes further: it resolves IDs across active AND trashed memories, explains that a non-empty `deleted_at` marks a trashed record, documents the `code: not_found` failure case, and states it is not rate-limited but requires an active subscription. That is meaningful behavioral context beyond structured fields.

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

Conciseness5/5

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

Front-loaded with the purpose and the truncation trigger, then the return shape, then edge cases (trash, not_found, rate limit, subscription). No sentence is filler; each adds a distinct operational fact.

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

Completeness5/5

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

For a single-parameter read tool with an output schema and full annotations, this covers everything an agent needs: when to call it, what it returns (content, metadata, topics, created_at), how trashed records behave, and the failure mode. 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% and the schema already tells the agent where memory_id comes from (store task results, search items, list pages). The description adds the truncation rationale for fetching by ID but no additional constraint on the ID format itself, so baseline 3 applies.

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

Purpose5/5

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

States a specific verb+resource ('Retrieve a single memory by its ID') and immediately scopes it as 'full, untruncated content', which distinguishes it from the search/list siblings that truncate. An agent can tell exactly which tool fetches a complete record.

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

Usage Guidelines5/5

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

Explicitly names the trigger condition: use after `amber_search_memories` when a result shows `truncated: true`, or when a memory_id is already known. It also names `amber_restore_memory` as the follow-up for trashed records, routing the agent correctly instead of guessing.

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

amber_get_store_task_statusCheck memory storage statusA
Read-onlyIdempotent
Inspect

Check the processing status of a memory store task. Call this before you list, search for or delete the facts you just stored, or when the user asks whether something was saved. Do NOT hold your reply to the user waiting for completed: an accepted store is already durable and finishes in the background, usually within a minute for a few facts. Not rate-limited. Read-only. Requires an active subscription.

Statuses, and these are the ONLY four: pending (accepted and queued, nothing written yet), processing (facts are being written; some are already stored), completed (every fact written), error (error says why). Poll until completed -- note the ed. The import tool's vocabulary (awaiting_upload, running, complete, failed) was published here by mistake until 2026-09-12; an agent waiting for complete waited for ever.

progress reads Stored N/M memories... while writing. Facts are embedded in one batch and then written one at a time, each write idempotent and retry-safe, so a QUEUE redelivery creates no duplicates. That is Amber retrying internally. A CLIENT retry is different: re-sending the same amber_store_memory call starts a NEW task and stores a second copy, so check this status before resending after a transport error.

Rows become listable and SEARCHABLE as they land, NOT only at completed: a partially written task already has some of its memories retrievable. Wait for completed before concluding a fact is absent.

Returns code: not_found if the task_id doesn't exist or has expired (tasks are pruned after 7 days).

NameRequiredDescriptionDefault
task_idNoUUID from `amber_store_memory`'s response. Poll this to know when memories are ready to search. Invalid IDs return `code: not_found`. Omit it only if you are passing `idempotency_key` instead.
idempotency_keyNoALTERNATIVE to `task_id`: the key you sent to `amber_store_memory`. Use this when you never received the response and so have no task_id -- which is the exact situation the key exists for. Without it the only way to resolve a key is to re-issue the whole store, which needs the payload you may no longer hold.

Output Schema

NameRequiredDescription
errorYesError message if status is error. Null otherwise.
statusYes`pending` queued, `processing` writing, `completed` all facts stored, `error` stopped (see `error`). Rows become listable and searchable AS THEY LAND, not only at `completed` -- a task mid-write already has some of its memories retrievable. Wait for `completed` before concluding a fact is ABSENT, which is the reason to wait; do not assume nothing is stored until then.
task_idYesThe task this report is about.
progressYesHuman-readable progress, e.g. 'Stored 4/14 memories...'. The count increments as memories are written. Null when pending, when completed, AND during the early phase of processing before the first memory is written -- so a null here does NOT mean the task is stuck; poll again.
fact_countYesHow many facts this task was created with. Once status is `completed`, memory_ids.length MUST equal this -- compare them. A shorter list means some facts did not resolve to a stored row, which is a real shortfall and not something to report as a successful save.
memory_idsYesMemory IDs created by this task. Null until completed.

TDQS

A4.4/5.0
Behavior4/5

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

Well beyond the annotations: it discloses durability semantics (an accepted store is already durable), the exact four status values with meanings, background completion timing, no rate limiting, subscription requirement, queue-redelivery idempotency vs client-retry duplicate risk, partial searchability during writing, and 7-day pruning with `code: not_found`. Annotations already cover read-only and idempotent, and the description is consistent with them; minor deduction because return-value detail partly duplicates the existing output schema.

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

Conciseness4/5

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

Front-loaded with purpose and the key instruction (don't block on completion), and the status vocabulary is laid out cleanly. Some phrasing is editorial rather than informative ('note the `ed`', 'waited for ever', 'That is Amber retrying internally'), which costs a little density but not comprehension.

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

Completeness5/5

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

For a polling tool with an output schema, everything an agent needs is present: when to call, how to interpret each status, how long to wait, what partial results mean, and how expiry/not_found is surfaced. Nothing material is missing for correct invocation.

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

Parameters3/5

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

Schema coverage is 100% and both parameters already document their roles, including the task_id/idempotency_key alternative, so the schema carries the burden. The description reinforces the idempotency_key case indirectly via the client-retry duplicate warning but adds no new syntax or format detail beyond the schema.

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

Purpose5/5

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

Opens with a specific verb and resource: 'Check the processing status of a memory store task.' It explicitly separates itself from the similarly-named import/export status siblings by calling out that the import tool's vocabulary ('awaiting_upload', 'running', 'complete', 'failed') was mistakenly published here, so an agent can route correctly without opening schemas.

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

Usage Guidelines5/5

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

Gives explicit when-to-use ('before you list, search for or delete the facts you just stored', 'when the user asks whether something was saved') and explicit when-not/wait guidance ('Do NOT hold your reply to the user waiting for completed'). It also states the polling rule and the prerequisite of an active subscription, 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.

amber_import_memoriesRestore memories from a backup URLA
Idempotent
Inspect

Restore an Amber backup from a URL. Amber reads the archive DIRECTLY from that URL and never stores a copy of it.

Use this when the user has their .json.gz backup somewhere reachable over HTTPS -- S3, R2, Backblaze, Google Cloud Storage, a self-hosted MinIO, or any presigned link. IF THE FILE IS ONLY ON THEIR COMPUTER this tool cannot reach it: send them to https://ambermem.com/backups, which explains how to upload it directly. Do not ask them to paste the file contents; a backup is far too large and it is not text you can retype.

THE LINK MUST STAY VALID FOR THE WHOLE RESTORE, which runs for hours on a large account -- tell the user to use one good for at least 30 days. Amber re-reads the URL as it works, so a link that expires part-way stops the restore.

SAFE TO RUN AGAIN. Memories keep the ids they had in the archive, so re-running cannot duplicate anything -- and a restore that stopped part-way resumes rather than starting over. This ADDS to the account; it never deletes or replaces what is already there.

Returns a task_id. Poll amber_get_import_status until it says complete, and tell the user the count when it does. Requires an active subscription.

Errors: invalid_params (the URL is not https, points back at Amber, carries credentials, cannot be read right now, does not support range requests, or what is behind it is not an Amber archive). All of that is checked BEFORE any task is created, so a rejected URL leaves nothing behind.

NameRequiredDescriptionDefault
urlYesHTTPS URL of the .json.gz backup, exactly as /backup produced it. Must be readable without credentials (a presigned link is ideal) and must support HTTP range requests, which every major object store does.

Output Schema

NameRequiredDescription
statusYesAlways `pending` here: the restore is queued, not done. Nothing has been written yet.
messageYesA human-readable summary of what was accepted and what happens next.
task_idYesPoll this with `amber_get_import_status` until it says `complete`.

TDQS

A4.8/5.0
Behavior5/5

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

Annotations cover idempotency and non-destructiveness, but the description adds far more: no copy is stored, the link must remain valid for the whole multi-hour restore and is re-read as work proceeds, a partial restore resumes, and the operation is additive only. It also discloses the subscription requirement and the full set of invalid_params causes, plus the fact that validation happens before any task is created.

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

Conciseness4/5

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

Front-loaded with the core action and scope, then organized into short scannable blocks for usage, link lifetime, re-run safety, return value, and errors. It is long, and the capitalized emphasis plus slight repetition about link validity cost a little, but nearly every sentence carries decision-relevant information for an agent.

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

Completeness5/5

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

Despite an output schema existing, the description still names the returned task_id and the required follow-up (poll amber_get_import_status until complete, then report the count), which closes the loop for a long-running async operation. Nothing an agent needs to call this correctly — prerequisites, link requirements, resume semantics, error handling — 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% and already documents HTTPS, credential-free access, and range-request support, so the schema does the heavy lifting. The description still adds operational meaning the schema lacks — that the link must survive at least 30 days for a large account, and that URLs pointing back at Amber or carrying credentials are rejected.

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 — restore an Amber backup from a URL — and immediately distinguishes it from the sibling paths (amber_upload_backup for local files, amber_export_memories for the reverse direction) by explaining that Amber reads the archive directly from the URL. An agent can route correctly without opening any schema.

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

Usage Guidelines5/5

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

Gives explicit when-to-use conditions (file reachable over HTTPS: S3, R2, Backblaze, GCS, MinIO, presigned link) and an explicit when-not with a named alternative destination (https://ambermem.com/backups for files only on the user's computer). It also pre-empts a wrong workaround ("Do not ask them to paste the file contents").

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

amber_list_deleted_memoriesList deleted memories (trash)A
Read-onlyIdempotent
Inspect

Browse the trash in reverse chronological order of deletion (most recently deleted first). Supports cursor pagination via after_id. has_more tells you whether another page exists. Use amber_restore_memory to bring an item back, or amber_search_deleted_memories for meaning-based search within the trash.

Each result includes full content (not truncated), metadata, topics, creation timestamp, and deletion timestamp. Pass metadata_filter for an EXHAUSTIVE exact-match filter over the trash (same as amber_list_memories). Read-only -- does not modify or permanently delete any memories. Not rate-limited. Requires an active subscription.

Returns memories array, next_cursor (null if no more pages), and has_more boolean.

Errors: invalid_input (a bad cursor, limit, or filter type).

NameRequiredDescriptionDefault
limitNoNumber of deleted memories per page (default 20, min 1, max 100). Use smaller values (5-10) for quick checks, larger values (50-100) for bulk review. Omit to use the default of 20.
after_idNoPagination cursor (memory ID string). Pass the `next_cursor` value from a previous response to fetch the next page. Omit on the first call to start from the most recently deleted memory. Do not fabricate cursor values.
metadata_filterNoOptional EXACT-match metadata filter, e.g. { "qa_batch": "2026-07-21" }. Values must be SCALARS (string/number/boolean/null) -- operator objects like {"$ne": ...} and nested objects are NOT supported (rejected with invalid_input). Matching is TYPE-STRICT as well as exact: metadata stored as the number 5 is matched by 5, NOT by "5". A null VALUE matches only memories that explicitly stored a null under that key -- a memory missing the key entirely does NOT match, exactly as a wrong value would not. If a value reached you stringified (from JSON, a config file, a spreadsheet export), convert it back first -- a type mismatch returns an empty result set that looks exactly like "no such data". Filters at the database level and returns EVERY matching trashed memory across pages (same exhaustive semantics as amber_list_memories).

Output Schema

NameRequiredDescription
has_moreYesWhether another page exists. Prefer this over guessing from the page size.
memoriesYesThis page of memories currently in the trash, newest first. Restore one with `amber_restore_memory`.
next_cursorYesPass this back as `after_id` for the next page (the parameter is `after_id`; this said `cursor` until 2026-09-19, which is not a parameter this tool has). Null on the last page. Opaque: do not build or edit one.

TDQS

A4.6/5.0
Behavior4/5

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

Annotations already declare readOnly/idempotent/non-destructive, but the description adds real context beyond them: subscription requirement, no rate limiting, full untruncated content in results, exhaustive database-level filtering semantics, and the specific `invalid_input` error condition. The only gap is it doesn't contrast behavior with hard-delete (amber_delete_memory) beyond asserting it never permanently deletes.

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 front-loaded — purpose and pagination first, alternatives second, semantics third. Slightly padded by restating return fields (`memories`, `next_cursor`, `has_more`) that an output schema already documents, and by re-explaining metadata_filter caveats already present in 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?

For a 3-parameter read tool with an output schema, the description covers scope, ordering, pagination, filtering semantics, auth requirement, and error modes. Nothing an agent needs to call it correctly is missing.

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

Parameters4/5

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

Schema coverage is 100%, so a 3 is the baseline, but the description adds cross-tool meaning: the metadata_filter behaves the same exhaustively as in amber_list_memories, and after_id/has_more pagination contract is spelled out. Marginal added value over the already-detailed schema, hence 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?

Opens with a specific verb+resource+scope: 'Browse the trash in reverse chronological order of deletion (most recently deleted first).' It is immediately distinguishable from amber_list_memories (live memories) and amber_search_deleted_memories (meaning-based search within trash), both of which are named.

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

Usage Guidelines5/5

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

Explicitly routes the agent: use amber_restore_memory to bring an item back, use amber_search_deleted_memories for meaning-based search. It also states the prerequisite (active subscription) and that it is not rate-limited, leaving little to infer about when to pick this tool.

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

amber_list_memoriesList active memories (chronological)A
Read-onlyIdempotent
Inspect

Browse all active memories in reverse chronological order (newest first). Use cursor-based pagination via after_id. has_more tells you whether another page exists. Prefer amber_search_memories when looking for specific content; use this when the user wants a browsing overview or asks "show me my recent memories".

Each result includes full content (not truncated), subjects, metadata, topics, event dates, and creation timestamp. Pass metadata_filter for an EXHAUSTIVE exact-match filter -- it returns every memory carrying the given metadata (search's metadata_filter is relevance-bounded and can miss low-scoring matches). Read-only -- does not modify any memories. Not rate-limited. Requires an active subscription.

Returns memories array, next_cursor (null if no more pages), and has_more boolean.

Errors: invalid_input (a bad cursor, limit, or filter type). A cursor from another account or an older schema is rejected rather than silently restarted from the beginning.

NameRequiredDescriptionDefault
limitNoNumber of memories per page (default 20, min 1, max 100). Use smaller values (5-10) for quick overviews, larger values (50-100) for bulk browsing. Omit to use the default of 20.
after_idNoPagination cursor (memory ID string). Pass the `next_cursor` value from a previous response to fetch the next page. Omit on the first call to start from the newest memory. Do not fabricate cursor values.
metadata_filterNoOptional EXACT-match metadata filter, e.g. { "qa_batch": "2026-07-21" }. Values must be SCALARS (string/number/boolean/null) -- operator objects like {"$ne": ...} and nested objects are NOT supported (rejected with invalid_input). Matching is TYPE-STRICT as well as exact: metadata stored as the number 5 is matched by 5, NOT by "5". A null VALUE matches only memories that explicitly stored a null under that key -- a memory missing the key entirely does NOT match, exactly as a wrong value would not. If a value reached you stringified (from JSON, a config file, a spreadsheet export), convert it back first -- a type mismatch returns an empty result set that looks exactly like "no such data". Filters at the database level and returns EVERY matching memory across pages -- use this (not amber_search_memories) when you want ALL memories tagged with a value. Search's metadata_filter is applied after semantic ranking, so it can miss low-scoring matches; this one cannot.

Output Schema

NameRequiredDescription
has_moreYesWhether another page exists. Prefer this over guessing from the page size, which is a valid full page on the last page too.
memoriesYesThis page of active memories, newest first.
next_cursorYesPass this back as `after_id` for the next page (the parameter is `after_id`; this said `cursor` until 2026-09-19, which is not a parameter this tool has). Null on the last page. Opaque: do not build or edit one.

TDQS

A4.6/5.0
Behavior4/5

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

Annotations already cover readOnly/idempotent/non-destructive, yet the description still adds non-obvious operational facts: not rate-limited, requires an active subscription, cursor rejection semantics for cross-account or old-schema cursors, and that full content is returned untruncated. The only minor gap is that it does not discuss page-size effects on latency or any ordering guarantees under concurrent writes, so it falls just short of the top.

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

Conciseness4/5

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

Front-loaded with purpose and routing before details, and nearly every sentence carries new information. It is somewhat long and repeats cursor/pagination mechanics already spelled out in the schema's after_id description, which is the main deduction.

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 scope, ordering, pagination contract, filter semantics, safety, prerequisites, error cases, and return shape for a 3-parameter tool with nested objects. Even though an output schema exists (making the return-value sentence partly redundant), nothing an agent needs in order to call this correctly is missing.

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

Parameters4/5

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

Schema coverage is 100% and the parameter descriptions are already very detailed, so the baseline is 3. The description still adds genuine meaning by contrasting this tool's metadata_filter (database-level, exhaustive) against search's post-ranking filter, which is a semantic distinction not stated in the 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?

States a specific verb and resource ('Browse all active memories') plus ordering ('reverse chronological, newest first'), and explicitly names the sibling it is not: amber_search_memories. An agent can distinguish browsing from searching without opening either schema.

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

Usage Guidelines5/5

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

Gives explicit routing rules: 'Prefer amber_search_memories when looking for specific content; use this when the user wants a browsing overview or asks "show me my recent memories"'. It also adds a second decision rule for metadata_filter (exhaustive vs relevance-bounded search), which is a real when-to-use distinction rather than an implication.

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

amber_list_subjectsList existing subjects (people)A
Read-onlyIdempotent
Inspect

List the subjects (people) already in memory, most-mentioned first, with a count and the topics their memories appear under. Call this before amber_store_memory or amber_search_memories when you're unsure how a person was recorded, and REUSE the existing spelling ("patrick vane") instead of a near-duplicate ("pat"/"patrick"/"patrick v"). Subject drives retrieval ranking, so consistent naming matters even more than for topics.

USE topics TO CATCH A SPLIT PERSON. Subjects match by EXACT text, so one human recorded under two names (a nickname, a maiden name, a first name then a full name -- e.g. "katie" and "yu pan") forks silently into two people, and every later search returns a plausible but INCOMPLETE answer. String similarity cannot catch that, because the two spellings need not share any characters. Overlapping topics are the signal: two subjects that both appear under e.g. "relationships" may be the same person. If you suspect a fork, confirm it with the USER (never merge on your own inference -- wrongly merging two real people is worse than the fork), then repair it with amber_rename_subject, which merges into the target name.

Read-only, not rate-limited. Requires an active subscription.

NameRequiredDescriptionDefault
limitNoMax subjects to return (default 200), most-mentioned first.

Output Schema

NameRequiredDescription
has_moreYesTrue when more subjects exist than were returned. Raise `limit` to see them. Without this a truncated list is indistinguishable from a complete one, and those mean opposite things: `amber_search_memories` tells you to check here for the exact spelling of a name, so "not in this list" has to be trustworthy.
subjectsYesExisting subjects, most-mentioned first.

TDQS

A4.6/5.0
Behavior5/5

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

Annotations already declare readOnly/idempotent/non-destructive, and the description adds context they do not carry: 'not rate-limited' and 'Requires an active subscription.' It also discloses the critical behavioral trait that subjects match by EXACT text and that forks produce silently incomplete search 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 core action and the reuse-existing-spelling rule are front-loaded, and every paragraph is on-topic. The fork-detection section is long relative to a one-parameter list tool, but it carries genuinely non-obvious guidance rather than filler.

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

Completeness5/5

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

An output schema exists, so return values need not be spelled out, and the description still covers ordering, the count/topics payload, the subscription prerequisite, and the exact-match pitfall. 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.

Parameters3/5

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

Schema coverage is 100% and the single `limit` parameter is fully documented in the schema, including default and ordering. The description only echoes the 'most-mentioned first' ordering, adding no new syntax or constraint beyond the schema, so the baseline 3 applies.

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

Purpose5/5

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

States a specific verb and resource ('List the subjects (people) already in memory') plus ordering ('most-mentioned first') and return shape ('a count and the `topics`'). It is clearly distinguishable from siblings like amber_search_subjects and amber_list_topics.

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

Usage Guidelines5/5

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

Explicitly says to call it before amber_store_memory or amber_search_memories when unsure how a person was recorded, and names amber_rename_subject as the repair path. It also gives a concrete when-to-use signal for the `topics` field (detecting split subjects) and an explicit prohibition (never merge on your own inference).

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

amber_list_topicsList existing topics/tagsA
Read-onlyIdempotent
Inspect

List the topics (tags) already in use, most-used first. Call this before amber_store_memory when you're unsure whether a topic already exists, and REUSE an existing name instead of minting a near-duplicate (reuse "work" rather than adding "career" or "job"). By default only topics with at least one live memory are shown; pass include_empty=true to also see orphaned 0-count topics (topics aren't deleted when their memories are). Consistent topics keep the topics filter in amber_search_memories reliable. Read-only, not rate-limited. Requires an active subscription.

NameRequiredDescriptionDefault
limitNoMax topics to return (default 200), most-used first.
include_emptyNoAlso include topics with 0 live memories (orphaned after their memories were deleted). Default false.

Output Schema

NameRequiredDescription
topicsYesExisting topics, most-used first.

TDQS

A4.8/5.0
Behavior5/5

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

Goes well beyond the readOnly/idempotent/non-destructive annotations by disclosing the default filter (only live topics), the existence of orphaned 0-count topics and why they persist, that the operation is not rate-limited, and that it requires an active subscription. The subscription prerequisite and default-hiding behavior are exactly the kind of context 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?

Front-loads the core behavior, then usage guidance, then defaults and constraints — good ordering with no filler. The single dense run-on covering defaults plus orphan explanation is slightly heavy but every clause carries 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 two-optional-parameter read tool with an output schema, the description covers purpose, prerequisites, defaults, and downstream usage. Nothing an agent needs to call it correctly is missing.

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

Parameters4/5

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

Schema coverage is 100%, so baseline is 3; the description earns above that by explaining the *reason* include_empty exists (topics aren't deleted when their memories are) and reinforcing most-used-ordering for limit. It adds rationale rather than new mechanical detail, so not quite 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?

States a specific verb and resource ('List the topics (tags) already in use, most-used first') and immediately differentiates itself from the sibling tools it interacts with (amber_store_memory, amber_search_memories). An agent can tell exactly what this returns without opening the schema.

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

Usage Guidelines5/5

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

Explicitly prescribes when to call it ('before amber_store_memory when you're unsure whether a topic already exists') and what to do with the result ('REUSE an existing name instead of minting a near-duplicate'), including a concrete example. It also names the downstream consumer of consistency (the `topics` filter in amber_search_memories), which is genuine when/when-not routing.

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

amber_manage_subscriptionGet subscription management URLA
Read-onlyIdempotent
Inspect

Return a PayPal URL the user can open to manage their subscription (update payment method, view billing history, change payment source) and the next billing date. Use this when the user asks about billing, wants to change their payment method, or wants to view their subscription details.

Unlike amber_cancel_subscription (which cancels) or amber_reactivate_subscription (which creates a new subscription), this tool only provides a link to PayPal's self-service page -- no changes are made by calling it.

The URL opens PayPal's subscription management page -- Amber does not handle payment details directly. Returns code: no_subscription if the account has no subscription on record (use amber_reactivate_subscription to start one). Read-only -- does not modify the subscription. Not rate-limited.

NameRequiredDescriptionDefault

No parameters

Output Schema

NameRequiredDescription
messageYesA human-readable summary of what the user will find at that link.
manage_urlYesA PayPal page where the user changes or cancels billing themselves. Relay it; Amber cannot act on their behalf there.
next_billing_dateYesWhen subscription_active is true, this is the next charge date. When false and in the future, the user has access until this date but no subscription. Null if unknown.

TDQS

A4.6/5.0
Behavior4/5

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

Annotations already declare readOnlyHint, idempotentHint, destructiveHint=false and openWorldHint, so the safety profile is covered; the description nonetheless adds real context beyond them: Amber never touches payment details, PayPal owns the self-service page, and a `code: no_subscription` result is possible. It loses a point for restating the read-only/no-side-effect property twice (once as 'no changes are made by calling it' and again as 'Read-only -- does not modify the subscription'), which duplicates what the annotations already say.

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?

Structure is good: purpose first, then when-to-use, then sibling differentiation, then failure/behavior notes. The only weakness is mild redundancy between 'no changes are made by calling it' and 'Read-only -- does not modify the subscription,' plus the standalone 'Not rate-limited' fragment, which could have been folded in.

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 document return values, yet it usefully names the two things returned (URL and next billing date) and documents the `no_subscription` error branch. For a zero-parameter read tool with rich annotations, nothing an agent needs in order 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?

There are zero input parameters, so there is no parameter meaning to add and the baseline is 4. The description correctly focuses on outcome (URL plus next billing date) rather than inventing parameter guidance that does not apply.

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 ('Return a PayPal URL... to manage their subscription') and enumerates the concrete actions enabled (update payment method, view billing history, change payment source). It then explicitly contrasts itself with `amber_cancel_subscription` and `amber_reactivate_subscription`, so an agent can disambiguate without opening any schema.

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

Usage Guidelines5/5

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

It gives an explicit trigger ('Use this when the user asks about billing, wants to change their payment method, or wants to view their subscription details') plus explicit when-not guidance via the two named sibling alternatives. It even routes the `no_subscription` failure case to `amber_reactivate_subscription`, which is exactly the kind of fallback routing an agent needs.

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

amber_mark_notification_readMark a developer notification as readA
DestructiveIdempotent
Inspect

Dismiss a developer notification after the user has acknowledged it. Permanently removes it from the developer_notifications block. That block is appended to a tool response ONLY WHEN there is at least one unread notification -- most responses will not contain it, and its absence means there is nothing pending, not that the feature is missing.

Side effects: deletes the notification row from the user's database. Idempotent in intent but returns code: not_found if already dismissed. Only call after the user has seen the notification content. Not rate-limited.

NameRequiredDescriptionDefault
notification_idYesPositive integer from the `id` field of a notification in `developer_notifications`. Example: if the block contains `{id: 42, message: "..."}`, pass 42. Each ID is unique and single-use.

Output Schema

NameRequiredDescription
statusYesAlways `read`: the notification is acknowledged and will not be attached to further responses.
notification_idYesThe notification that was acknowledged.

TDQS

A4.4/5.0
Behavior5/5

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

Adds substantial context beyond the annotations: it names the database effect (row deletion), the specific error shape on repeat calls (`code: not_found`), and that the tool is not rate-limited. It even reconciles the idempotentHint annotation with the not_found behavior ('idempotent in intent but...'), which is precisely the nuance an agent needs.

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

Conciseness4/5

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

Front-loads the action and consequence, then side effects and preconditions. The developer_notifications explanation is long-ish but earns its place because absence of the block is easily misread by agents. No filler sentences.

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

Completeness5/5

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

For a single-parameter destructive mutation, the description covers precondition, persistence effect, error behavior, rate limits, and the confusing absence-of-block case. With annotations and an output schema also present, 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 coverage is 100% and the schema already explains that the value is a positive integer from the `id` field, with a worked example. The description adds only the surrounding context of where that ID lives, not new parameter semantics, so baseline 3 applies.

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

Purpose5/5

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

States a specific verb and resource ('Dismiss a developer notification') and immediately specifies what that means ('Permanently removes it from the `developer_notifications` block'), so an agent knows exactly what the call 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?

Gives an explicit prerequisite ('Only call after the user has seen the notification content') and clarifies when the notification block is present vs absent. No sibling tool offers a comparable action, so no alternative routing is needed, but there is no guidance on what to do instead when the block is absent.

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

amber_reactivate_subscriptionSubscribe or resubscribeAInspect

Create a PayPal subscription and return an approval URL. The user MUST open this URL in their browser to activate. Do NOT use if the user just wants to manage an existing subscription -- use amber_manage_subscription instead.

First-time: 90-day free trial, then $2.99/month. Returning: preserves remaining free days, or bills immediately if expired. Warn the user to log in with the SAME PayPal account they originally signed up with.

No charge until the user approves. Returns code: already_active if active, code: deletion_scheduled if deletion is pending. Each call creates a NEW pending subscription with its own approval URL, so give the user the latest one. Approving one cancels any other active subscription on the same account, so an older link approved by mistake cannot bill twice.

Errors: creation_failed (PayPal would not create the subscription; nothing was charged and retrying is safe).

NameRequiredDescriptionDefault

No parameters

Output Schema

NameRequiredDescription
statusYesAlways `approval_needed`: a subscription was created and is waiting for the user to approve it at PayPal. Nothing is charged and nothing is active until they do.
messageYesA human-readable summary, including the approval link the user has to open.
approval_urlYesURL the user must open to approve the subscription. ALWAYS share this URL with the user.

TDQS

A4.8/5.0
Behavior5/5

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

Goes well beyond the annotations by disclosing pricing/trial terms, that no charge occurs before approval, that each invocation mints a new pending subscription and invalidates older links, that approving one cancels other active subscriptions on the account, and specific status/error codes (`already_active`, `deletion_scheduled`, `creation_failed`) with retry safety. The non-idempotent behavior is explicitly justified by 'each call creates a NEW pending subscription', which aligns with `idempotentHint: false`.

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

Conciseness4/5

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

Front-loads the action and the hard requirement, then layers first-time/returning behavior, status codes, and errors in a scannable order. It is dense with genuinely useful facts, though a few lines (the double-billing explanation) are longer than needed for an agent to act correctly.

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

Completeness5/5

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

Despite an output schema existing, the description usefully pre-declares the branches an agent must handle (`already_active`, `deletion_scheduled`, `creation_failed`), plus the human-in-the-loop browser step that annotations alone would never convey. For a zero-param, open-world, non-idempotent mutation, 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?

The tool takes zero parameters, so the 4 baseline applies; there is no input surface for the description to clarify further. The description correctly spends its words on flow branching and outcomes rather than inventing parameter details.

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 plus the concrete return artifact: 'Create a PayPal subscription and return an approval URL.' It immediately distinguishes itself from `amber_manage_subscription`, so an agent can route between the two without opening either schema.

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

Usage Guidelines5/5

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

Gives explicit when-to-use ('first-time' vs 'returning' flows), explicit when-not ('Do NOT use if the user just wants to manage an existing subscription'), and names the sibling alternative to use instead. It also states the required post-call user action (must open the URL in a browser) and the account-matching warning.

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

amber_rename_subjectRename a subject (person) everywhereAInspect

Relabel a subject (person) across EVERY memory tagged with them -- use when a name changes, or when you learn more of it (you stored facts under "sean" and later learn it is "sean okafor"). Subject retrieval is EXACT-MATCH, so a bare first name or an outdated name is a latent retrieval miss; this fixes all of that person's memories at once. If some of those memories already carry new_subject, they MERGE onto the one name -- merged_into_existing reports how many. NEVER deletes a memory. A plain rename (target name unused) is fully reversible by renaming back. A MERGE IS NOT REVERSIBLE: once two names are one, renaming back moves ALL of those memories to the old name, including the ones that never had it -- the original split cannot be restored through this tool. So when merged_into_existing would be > 0 you are performing a ONE-WAY operation: be correspondingly sure, and prefer confirming with the user first. Both names match case-insensitively and are normalized the same way stored subjects are (call amber_list_subjects to see exact spellings first). Also relabels the person on any of their memories currently in the trash, so restoring one later stays consistent -- so renamed_count (total memories relabelled) can exceed the active count shown by amber_list_subjects. Returns renamed_count; 0 (a no-op, not an error) if no memory was tagged with old_subject. Requires an active subscription.

Errors: invalid_input (a name is empty, too long, or carries control characters).

NameRequiredDescriptionDefault
new_subjectYesThe corrected / updated full name, e.g. "sean okafor".
old_subjectYesThe subject as currently stored, e.g. "sean" (case-insensitive; check amber_list_subjects).

Output Schema

NameRequiredDescription
statusYesAlways `renamed`, including when `renamed_count` is 0. Matching nothing is a no-op rather than a failure.
new_subjectYesThe normalized new subject applied.
old_subjectYesThe normalized old subject that was matched.
renamed_countYesTotal memories relabelled from old_subject to new_subject (active + trashed).
renamed_activeYesHow many of renamed_count were ACTIVE memories -- this is the number that matches what amber_list_subjects reports.
renamed_trashedYesHow many of renamed_count were in the trash. Trashed memories are relabelled too so a later restore stays consistent; this is why renamed_count can exceed the active count.
merged_into_activeYesOf merged_into_existing, how many are ACTIVE -- this is the figure that reconciles with amber_list_subjects, which counts active memories only.
merged_into_trashedYesOf merged_into_existing, how many are in the trash. Trashed rows are counted because they are relabelled too, which is why the total can exceed what amber_list_subjects shows.
merged_into_existingYesMemories that ALREADY carried new_subject before this call, counting any that carried BOTH names (on those the two tags simply became one). When >0 the two names are now one person, not a plain rename -- surface that to the user, since merging two genuinely different people is hard to notice and worse than leaving them split.

TDQS

A4.8/5.0
Behavior5/5

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

Goes well past the annotations, disclosing merge behavior, the irreversibility of a merge, that deleted memories are also relabelled (so renamed_count can exceed the active count), that a no-op returns 0 rather than an error, the subscription requirement, and the invalid_input conditions. The destructiveHint=false annotation is consistent with the stated "NEVER deletes a memory"; the description productively refines that by explaining that irreversibility arises from merging, not deletion.

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 purpose and the exact-match retrieval rationale are front-loaded, and the risk disclosure follows in a logical order. It is a dense block of prose with some repetition around the merge warning, so it is efficient rather than optimal.

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 two-parameter mutation with an output schema already present, the description covers everything an agent needs: when to call it, what it returns, what it touches (including trashed memories), what is reversible, and what error to expect. Nothing material is left to inference.

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

Parameters4/5

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

With schema coverage at 100% the schema already documents both parameters, so the baseline is 3. The description still adds real meaning: shared case-insensitive matching and normalization of both names, plus the invalid_input failure modes (empty, oversized, control characters) that the schema does not state.

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 ("Relabel a subject (person) across EVERY memory tagged with them") and makes the scope explicit, which separates it cleanly from amber_list_subjects, amber_search_subjects, and amber_update_memory. The parenthetical example of a first name becoming a full name removes any ambiguity about what 'rename a subject' means in practice.

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 both trigger cases (a name changed, or you learned more of it) and names the prerequisite alternative, amber_list_subjects, for confirming exact spellings. It also supplies an exclusion-style warning: when merged_into_existing would be > 0, be sure and confirm with the user first, which is actionable decision guidance rather than generic advice.

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

amber_restore_memoryRestore deleted memoriesA
Idempotent
Inspect

Un-delete memories from the trash, making them searchable and visible in amber_list_memories again. Use after amber_search_deleted_memories or amber_list_deleted_memories to recover accidentally deleted content. Do NOT use for new content -- use amber_store_memory instead.

Provide memory_id (single) or memory_ids (batch, max 100). At least one is required. Idempotent: already-active memories are skipped. Side effects: moves memories from trash to active. VERIFY WHAT CAME BACK: the reply carries restored -- the full content of every memory this call actually un-trashed. READ IT and confirm it is the one the user meant, because an id proves nothing. EVERY ID you send is accounted for: restored_count plus already_active_ids plus not_found_ids. ALWAYS check restored_count against how many IDs you sent -- a batch is NOT all-or-nothing. If the IDs are already active you get restored_count: 0 with an explanatory message (a no-op, NOT an error); code: not_found is returned only when no memory exists with those IDs -- so a no-op is distinguishable from wrong IDs. Requires an active subscription. Not rate-limited.

Errors: invalid_params (no id supplied, or both singular and plural given); a wrong TYPE answers invalid_input from the schema layer. Restoring something that was never deleted is not an error either; it comes back in already_active_ids.

NameRequiredDescriptionDefault
memory_idNoUUID of a single memory to restore. Omit if using `memory_ids` for batch.
memory_idsNoArray of memory UUIDs to restore in one call (max 100). Omit if using `memory_id` for single.

Output Schema

NameRequiredDescription
statusYesAlways `restored`, including when nothing changed because every id was already active.
restoredYesThe memories this call actually brought back, in full. CHECK THESE: a count cannot tell you which memory was recovered, and that is the whole question after a wrong delete. Empty when nothing was restored -- never absent, so there is one shape to handle.
not_found_idsNoIDs that matched NO memory. Present only when some did not resolve. Most often these are from a store that has not finished writing yet (amber_store_memory is async) rather than wrong IDs -- check amber_get_store_task_status and retry.
restored_countYesHow many memories this call actually restored. Compare against the number of IDs you sent: any shortfall is itemised in already_active_ids / not_found_ids.
already_active_idsNoIDs that exist but were ALREADY active, so this call was a no-op for them. Present only when some were.

TDQS

A4.8/5.0
Behavior5/5

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

Goes well beyond the annotations: discloses the side effect (moves memories from trash to active), subscription requirement, absence of rate limiting, no-op vs not_found distinction, and non-atomic batch behavior. The idempotentHint is expanded into concrete mechanics (already-active entries return in already_active_ids) rather than merely restated.

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

Conciseness4/5

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

Front-loaded with purpose and when-to-use, then parameter mode, then return-value verification, then errors — a logical order with no filler sentences. It is long and heavy on emphatic capitalization, which is slightly excessive for the amount of distinct information conveyed.

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

Completeness5/5

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

Even with an output schema present, the description explains how to reconcile the counts (restored + already_active_ids + not_found_ids) and warns that batches are not all-or-nothing — exactly the reasoning an agent needs before trusting a restore result. Error taxonomy and the no-op-not-an-error case are both covered.

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

Parameters4/5

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

Schema coverage is 100%, so baseline would be 3, but the description adds a real constraint the schema does not express: the schema declares zero required properties, while the description states 'At least one is required' and that supplying both singular and plural triggers invalid_params.

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

Purpose5/5

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

States a specific verb and resource ('Un-delete memories from the trash') plus the resulting state change ('making them searchable and visible in amber_list_memories again'), which immediately distinguishes it from amber_search_deleted_memories and amber_list_deleted_memories.

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

Usage Guidelines5/5

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

Explicitly names the triggering workflow ('Use after amber_search_deleted_memories or amber_list_deleted_memories to recover accidentally deleted content') and gives an explicit exclusion with the correct alternative ('Do NOT use for new content -- use amber_store_memory instead').

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

amber_revoke_backup_keyRevoke the backup keyA
DestructiveIdempotent
Inspect

Revoke this account's backup key. Any automated job using it stops working immediately. Use this if a key may have leaked -- for example it was committed to a repository or pasted into a log. Returns code: not_found if no key exists. To rotate instead of simply revoking, call amber_create_backup_key, which replaces the old key in one step. Not rate-limited.

NameRequiredDescriptionDefault

No parameters

Output Schema

NameRequiredDescription
messageYesA human-readable summary of what happened.
revokedYesTrue if a key existed and is now revoked. False means there was nothing to revoke, which is reported as `code: not_found` rather than as success.

TDQS

A4.9/5.0
Behavior5/5

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

Annotations already declare destructive=true and idempotent=true, but the description adds the real blast radius: dependent automated jobs stop working immediately. It also discloses the not-found outcome ('Returns `code: not_found` if no key exists') and the operational trait 'Not rate-limited', none of which appear in structured fields.

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

Conciseness5/5

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

Four tight sentences, front-loaded with the action and its consequence, then the use case, the error case, and the alternative. No sentence is filler; the rate-limit note is short and load-bearing.

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?

An output schema exists, so return values need no explanation, and the description fills the remaining gaps: consequence of revocation, when to prefer rotation, error behavior, and rate-limit status. For a zero-param mutation tool, nothing an agent needs before invoking is missing.

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

Parameters4/5

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

The tool takes zero parameters, so there is nothing for the description to disambiguate beyond what the empty schema shows. The baseline for a parameterless tool applies, and the description correctly avoids inventing argument details.

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 ('Revoke this account's backup key') and immediately scopes it to a single key, which separates it from sibling tools like amber_delete_account. It also names the related rotation tool, so an agent can tell revoke apart from rotate without reading either schema.

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

Usage Guidelines5/5

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

Gives an explicit trigger ('Use this if a key may have leaked -- for example it was committed to a repository or pasted into a log') and names the alternative with the condition that selects it ('To rotate instead of simply revoking, call amber_create_backup_key'). When-to-use, when-to-rotate, and the sibling route are all stated.

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

amber_search_deleted_memoriesSearch deleted memories (trash)A
Read-onlyIdempotent
Inspect

Search within the trash for soft-deleted memories. Useful when the user asks about something they've since deleted. Results are ordered by relevance (higher score = better match; scores are relative within a single query). Optional max_age_days restricts to recently deleted items. Optional topics filters by topic (semantic matching). For ranking quality it takes the SAME optional steering params as amber_search_memories -- subjects, query_variants, keywords -- so trash ranking matches active search. Content is truncated to 1000 chars -- use amber_get_memory for full content. Use amber_restore_memory to bring a result back. Rate-limited (search bucket: 5000 capacity, refills ~1 per 17 seconds).

Errors: invalid_input (a parameter has the wrong type, or a date could not be parsed).

NameRequiredDescriptionDefault
queryYesNatural-language question or description of what to find. Use full sentences, not keywords.
topicsNoOptional topic filter. Pass category names like 'food', 'work', 'beliefs'. Matched semantically.
keywordsNoOptional EXACT lexical terms for the keyword (full-text) side of hybrid search -- the distinctive words the answer would contain. OMIT the subject's name (that is what `subjects` is for).
subjectsYesREQUIRED, exactly as in amber_search_memories -- same parameter, same ranking role, same contract. Who is this query about? e.g. ["patrick vane"], or ["self"]/["me"] for the USER. Pass [] (or ["anyone"]) when the query has no personal focus -- that is a deliberate answer, not an omission. BOOSTS the matching person's trashed facts; never hard-filters, so a wrong guess cannot hide a memory.
n_resultsNoMaximum results (default 10, max 100).
near_dateNoOptional ISO 8601 datetime to rank results by TEMPORAL PROXIMITY to (memories whose event_start/event_end sits closest rank higher), exactly like amber_search_memories. Pure boost; dateless memories are left in their existing relative order, though anchoring renormalises the whole set so their absolute scores move slightly (measured: about 10% lower).
max_age_daysNoOnly consider memories deleted within the last N days.
near_date_endNoOptional ISO 8601 datetime. With `near_date`, proximity is measured to the window [near_date, near_date_end]. REJECTED with invalid_input if `near_date` is not also given; must be on or after near_date.
query_variantsNoOptional alternative phrasings of the SAME query that YOU generate -- each is embedded and searched and the results fused, exactly like amber_search_memories.
metadata_filterNoOptional EXACT-match metadata filter. Values must be SCALARS (string/number/boolean/null) -- operator objects like {"$ne": ...} and nested objects are NOT supported (rejected with invalid_input). Matching is TYPE-STRICT as well as exact: metadata stored as the number 5 is matched by 5, NOT by "5". A null VALUE matches only memories that explicitly stored a null under that key -- a memory missing the key entirely does NOT match, exactly as a wrong value would not. If a value reached you stringified (from JSON, a config file, a spreadsheet export), convert it back first -- a type mismatch returns an empty result set that looks exactly like "no such data".

Output Schema

NameRequiredDescription
resultsYesMatching memories from the trash, best first. Bring one back with `amber_restore_memory`. Empty is not an error.

TDQS

A4.9/5.0
Behavior5/5

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

Annotations already declare readOnlyHint/idempotentHint, and the description adds substantial behavior beyond them: relevance ordering with scores only comparable within one query, 1000-char content truncation, and a concrete rate-limit budget (5000 capacity, ~1 refill per 17s). It also documents the invalid_input error class, which the 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?

Front-loaded with purpose, then usage trigger, then ranking/filter semantics, then operational limits and errors — a sensible order. It is dense and slightly long, but nearly every clause carries information an agent needs; only the repeated 'exactly like amber_search_memories' framing is mildly redundant.

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

Completeness5/5

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

For a 10-parameter search tool with a nested metadata_filter object, the description covers the gaps the schema leaves open: ranking interpretation, truncation, rate limits, error surface, and the restore path. Since an output schema exists, return-value structure need not be restated, so nothing material 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 cross-tool meaning the schema cannot: subjects/query_variants/keywords are the SAME steering contract as amber_search_memories, and topics is semantic rather than exact. It also flags max_age_days as a recency restriction, clarifying the interplay of filters versus ranking boosts.

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: 'Search within the trash for soft-deleted memories.' This cleanly separates it from amber_search_memories (active memories) and amber_list_deleted_memories (enumeration without query). An agent can pick the right tool from the first sentence alone.

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

Usage Guidelines5/5

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

Gives an explicit trigger ('when the user asks about something they've since deleted') and names the follow-up alternatives with conditions: amber_get_memory for full content past the 1000-char truncation, amber_restore_memory to bring a result back. It also explains that steering params mirror amber_search_memories, so the agent knows behavior transfers across tools.

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

amber_search_memoriesSearch memoriesA
Read-onlyIdempotent
Inspect

Find active memories by semantic meaning. Write the query as a natural-language question or description, NOT as keywords. Good: "What are the user's dietary preferences?", "meetings the user had last week". Bad: "diet food preferences", "meeting notes". Recall depends on YOU expanding the query: pass alternative phrasings in query_variants (synonyms, related terms, and sub-questions), the same way you attach variants when storing. Amber searches the query plus every variant and fuses the results -- it does NOT expand the query for you. For exact-term precision, also pass the distinctive lexical terms the answer would contain (key nouns, product names, numbers, IDs) in keywords -- and OMIT the subject's name from them (use subjects for that). Supports optional metadata filtering (e.g. { "user_tag": "work" }). Results are ordered by relevance (higher score = better match). Scores are only meaningful for ranking within a single query, not across different queries. IMPORTANT -- results are RANKED, never FILTERED: Amber always returns the closest n_results memories it has, even when none of them actually answer the query. A returned memory is therefore NOT evidence that the answer is stored, and a low score is not a reliable absence signal (scores aren't comparable across queries). YOU must judge relevance by READING the content: if no returned memory actually states the fact you asked about, treat it as NOT in memory. This matters most for the mandatory search-before-store check -- only count a fact as already stored when a returned memory genuinely states it, not merely because results came back. Ask for fewer results (n_results) if the padding is noisy. Use amber_list_memories for chronological browsing. When several results describe the SAME subject at different points in time, read them as a TIMELINE of how it evolved -- a more recent memory updates an older one, it does NOT contradict it. Each result carries created_at (and event_start/event_end when known); use those timestamps to order the progression rather than treating differing facts as a conflict. Optional topics param: pass topic names (like when storing) to focus on a category -- matched SEMANTICALLY against topics that already exist, so "beliefs" also matches "opinions"/"superstitions". It restricts the SEMANTIC half of the search to memories under the nearest topics and gives closer ones a BOUNDED lift (at most 1.5x) -- it does NOT re-sort by topic, and the keyword half is not restricted, so off-topic memories can still appear. Do not expect the on-topic memory at #1: a memory that answers the query better still wins, which is deliberate (a wrong topic guess must never bury the true answer). There is NO similarity floor: your name is matched to the NEAREST existing topics whatever their similarity, so a name matching nothing conceptually still resolves to the closest topics rather than to none. Check topics_resolved -- it reports the similarity of each match, and a low number there means your name found nothing real. A poor match cannot bury the true answer, because the lift is bounded and the keyword half is never restricted, but it does not silently become a no-op either. Omit if unsure. Content is truncated to 1000 chars in search results -- use amber_get_memory for full content. Rate-limited (drip bucket: 5000 capacity, refills ~1 token per 17 seconds).

Errors: invalid_input (a parameter has the wrong type, or a date could not be parsed). An empty result is NOT an error and comes back as an ordinary response with no hits. It is also RARE, because ranking pads results rather than filtering them, so treat it as "this account has nothing to rank" -- a new or emptied store, or a metadata_filter that excluded everything -- rather than as a stronger absence signal than a padded result.

NameRequiredDescriptionDefault
queryYesNatural-language question or description of what to find. Use full sentences, not keywords.
topicsNoOptional topic hint. Pass category names like 'food', 'work', 'beliefs'. What it actually does, in order: (1) each name is matched SEMANTICALLY against the topics that already exist and have live memories -- 'hobbies' resolves to 'gaming'; (2) the SEMANTIC half of the search is restricted to memories carrying one of the ~10 nearest topics, so on a store with many topics this genuinely narrows, while on a small store it may narrow nothing; (3) the keyword half is NOT restricted, so an off-topic memory can still appear via an exact word match; (4) closer topics get a bounded lift of at most 1.5x. That lift is a TIE-BREAKER and will NOT override relevance: a result scoring clearly better on the text still wins, so do not expect the on-topic memory to jump to #1. A synonym's edge over a near-neighbour topic is only a few percent; an EXACT existing name is far stronger. So if you need the topic to actually decide the order, call amber_list_topics and pass the exact name. The response reports `topics_resolved` -- what each name you passed matched -- so you can see how it was interpreted instead of guessing. A name matching nothing leaves the ranking unrestricted. Omit if unsure.
keywordsNoOptional EXACT lexical terms for the keyword (full-text) side of hybrid search -- the distinctive words the answer would contain: key nouns, product names, numbers, IDs, codes. E.g. for "what car does the user drive?" pass ["car", "drive"]. Pick them yourself and OMIT the subject's name (that's what `subjects` is for -- a name matches every fact about a person and only adds noise). If omitted, Amber extracts keywords heuristically. Semantic matching still runs on the query + query_variants; keywords only sharpen exact-term precision.
subjectsYesREQUIRED. Who is this query about? An ARRAY -- usually ONE person, e.g. ["patrick vane"]. Routes ranking so the right person's facts surface. Use ["self"] (or ["me"]) when the query is about the USER (e.g. "what foods is the user allergic to?") -- lifts the user's OWN facts above facts about family or others. Use a person's FULL name (e.g. ["patrick vane"]) when the query is about them: subjects are matched by EXACT (case-insensitive) text, so it must match how they were stored -- a bare first name can miss them or hit a different person. Call amber_list_subjects to see the exact names already stored. Use ["anyone"] or [] for a general query with no personal focus (e.g. "good pasta recipes"). List several only when the query is genuinely about multiple people together. BOOSTS matching memories, never hard-filters -- other subjects still appear, lower down.
n_resultsNoMaximum number of results to return (default 10, max 100).
near_dateNoOptional ISO 8601 datetime to rank results by TEMPORAL PROXIMITY to -- memories whose own event time (event_start/event_end) sits closest to this rank higher. Use it when the query has a time focus that plain wording can't resolve: for "where does the user work NOW?" pass today's date so the current job outranks an older one; for "what car did they drive back in 2019?" pass "2019-01-01". Memories with no event date keep their relative order among themselves, though their absolute scores move slightly (measured: about 10% lower under any anchor, because anchoring renormalises the whole result set), and a far-in-time memory is never pushed BELOW an unboosted peer, though its absolute score drops 5-10% under renormalisation -- the ordering claim holds, the score claim did not, and this sentence used to make the stronger one. Pass it whenever the question genuinely has a time focus -- ESPECIALLY for relative-time wording ("recently", "lately", "last week"), because that wording carries no meaning the text search can match, so the anchor is the only thing that can find the right memory. Do NOT pass it for a time-agnostic question: dated memories can then surface above undated ones that answer it better. Omit when in doubt.
near_date_endNoOptional ISO 8601 datetime. When set together with `near_date`, proximity is measured to the WINDOW [near_date, near_date_end] instead of a single instant (a memory whose event overlaps the window gets the full boost). Use for period queries like "what happened during 2023?" (near_date 2023-01-01, near_date_end 2023-12-31). REJECTED with invalid_input if `near_date` is not also given -- it marks the end of a window, so on its own it is a mistake rather than something to silently drop. Must be on or after near_date.
query_variantsNoAlternative phrasings of the SAME query that YOU generate -- synonyms, related terms, and any sub-questions -- exactly like the variants you attach when storing. Amber embeds and searches every one and fuses the results, so a memory worded differently than the user's phrasing still surfaces. Example: for "what does the user eat?" pass ["the user's dietary preferences", "foods the user likes", "what food does the user avoid?"]. Aim for 3-6; this replaces the automatic query expansion Amber used to do server-side.
metadata_filterNoOptional EXACT-match metadata filter. Matches memories whose metadata contains every key-value pair given. Matching is TYPE-STRICT: metadata stored as the number 5 is matched by 5, NOT by "5". A null VALUE matches only memories that explicitly stored a null under that key -- a memory missing the key entirely does NOT match. Values must be SCALARS (string/number/boolean/null) -- operator objects like {"$ne": ...}/{"$gte": ...} and nested objects are NOT supported (rejected with invalid_input). NOTE: applied AFTER semantic ranking, so it only filters the top relevance candidates -- a matching memory that scores low against the query can fall out. For an EXHAUSTIVE "give me everything tagged X", use amber_list_memories with its metadata_filter instead. WHEN (and only when) you pass metadata_filter, the response adds `filter_matched_total` (how many memories the filter matches in total) and `filter_truncated` -- if `filter_truncated` is true you are looking at a PARTIAL set and must NOT report it as complete; re-run via amber_list_memories to get all of them. NOTE: no value of n_results makes a filtered search exhaustive -- the filter is applied to a bounded relevance candidate pool, so amber_list_memories is the only exhaustive path.

Output Schema

NameRequiredDescription
resultsYesMatches, best first. An empty array means nothing matched, which is an ordinary result rather than an error.
topics_resolvedNoOnly when `topics` was supplied: what each requested topic name actually matched among the topics that exist. Topic names are matched semantically, so a synonym resolves to a NEARBY topic ('hobbies' -> 'gaming'); check `matched` to see how your name was interpreted. Because the topic boost is a bounded tie-breaker, a correct resolution can still leave the ranking unchanged -- if you need topics to decide the order, pass an EXACT name from amber_list_topics.
filter_truncatedNoOnly when metadata_filter was supplied: true when filter_matched_total exceeds the number of results returned. Use `amber_list_memories` with the same metadata_filter to get the complete set.
filter_matched_totalNoOnly when metadata_filter was supplied: how many active memories match that filter IN TOTAL, regardless of how many ranked into this page.

TDQS

A4.5/5.0
Behavior4/5

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

Goes far beyond the readOnly/idempotent/non-destructive annotations: discloses that results are ranked rather than filtered (with the anti-inference warning), 1000-char truncation, score non-comparability across queries, the drip-bucket rate limit, `filter_truncated` semantics, and topic lift bounds. The near_date paragraph, however, is self-referentially muddled ("the score claim did not, and this sentence used to make the stronger one"), leaving the agent unsure which claim currently holds — that undercuts otherwise excellent disclosure.

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?

Front-loaded reasonably at the top, but the body is bloated and repeats schema parameter prose nearly verbatim, then drifts into corrective meta-commentary about its own earlier wording. Sentences like "the ordering claim holds, the score claim did not, and this sentence used to make the stronger one" cost the reader without adding callable 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 9-parameter hybrid-search tool with nested `metadata_filter`, this covers the full decision surface: relevance judgement, absence inference, temporal ordering/timeline reading, error semantics, and how to get complete result sets. Nothing an agent needs in order to call it correctly is missing, and the output schema means return shape needn't be spelled out.

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%, so the baseline is 3, but the description adds real decision-level meaning: query must be natural language not keywords, `query_variants` replaces server-side expansion and should carry 3-6 phrasings, `keywords` must omit the subject name, and `subjects` uses exact case-insensitive matching with the `self`/`anyone` conventions. It also explains the topics-resolved/lift interaction that the schema alone does not make actionable.

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?

Opening sentence gives a specific verb+resource+scope: "Find active memories by semantic meaning." It then explicitly separates itself from siblings, naming `amber_list_memories` for chronological browsing and `amber_get_memory` for full content, so an agent can route correctly without opening another schema.

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

Usage Guidelines5/5

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

Gives explicit when-to-use guidance (search-before-store check, relative-time queries need `near_date`), explicit when-not (don't pass `near_date` for time-agnostic questions, omit `topics` if unsure), and names the alternatives `amber_list_memories` (exhaustive filtered listing), `amber_list_topics`, and `amber_list_subjects`. It even specifies the diagnostic escape hatch per parameter.

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

amber_search_subjectsSearch subjects (people) by nameA
Read-onlyIdempotent
Inspect

Find subjects (people) already in memory WITHOUT listing them all. Same data as amber_list_subjects -- name, memory count, and the topics their memories appear under -- just filtered. Prefer this over amber_list_subjects once a store has grown: listing every subject with its topics costs thousands of tokens on every store, and you usually only need to check one person.

MATCHING -- one rule: NO * means CONTAINS, and * is an anchor. • vane -> contains: finds "patrick vane" AND "robin vane" • van* -> starts with • *vane -> ends with • patrick*vane -> starts with one, ends with the other • * or an empty query -> everything (same as amber_list_subjects) Case- and accent-insensitive: andre finds "andré bremer". Spaces, hyphens, apostrophes and periods are IGNORED on both sides when comparing, so vanderberg and van-der berg both find "van der berg". Word ORDER still matters: berg van does NOT match it. Anchors apply to that compared form, so *jr DOES match "van der berg jr." -- the trailing period is not part of what is compared.

IT MATCHES TEXT, NOT PEOPLE. It cannot tell you that "pat" is "patrick vane" -- unrelated spellings of one human (a nickname, a maiden name) share no characters, so no text search finds them. To catch a person split across two names, list subjects and compare their topics, as amber_list_subjects explains.

Read-only, not rate-limited. Requires an active subscription.

NameRequiredDescriptionDefault
limitNoMax subjects to return (default 200), most-mentioned first.
queryYesName fragment to look for. No `*` = contains. `van*` = starts with, `*vane` = ends with. Empty or `*` returns everything.

Output Schema

NameRequiredDescription
has_moreYesTrue when total_matched exceeds the returned page.
subjectsYesMatching subjects, most-mentioned first.
total_matchedYesHow many subjects matched in total, BEFORE `limit` was applied. Greater than subjects.length means the list is truncated.

TDQS

A4.4/5.0
Behavior3/5

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

Annotations already declare readOnlyHint, idempotentHint, and destructiveHint=false, so safety is covered structurally. The description adds genuinely new operational facts — "not rate-limited" and "Requires an active subscription" — which is real value beyond the annotations, but it says nothing about result ordering or truncation behavior beyond what the schema states.

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

Conciseness4/5

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

Front-loaded purpose sentence, then clearly delimited MATCHING and limitation sections with bulleted examples — easy to scan. It runs long and repeats the "same as amber_list_subjects" equivalence twice, but the length is buying disambiguation rather than filler.

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

Completeness5/5

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

For a two-parameter search tool with an output schema, the description covers everything an agent needs to call it correctly: matching semantics, fallback path, rate-limit and subscription prerequisites, and an explicit statement of what the tool cannot do. No material gap remains.

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 substantial meaning the schema does not: normalization rules (case- and accent-insensitive, spaces/hyphens/apostrophes/periods ignored on both sides, word order still significant) and worked examples. The only gap is the `limit` parameter, whose semantics are left entirely to the schema.

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

Purpose5/5

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

Opens with a specific verb and resource plus scope: "Find subjects (people) already in memory WITHOUT listing them all," and immediately states the returned fields (name, memory count, topics). It names the sibling amber_list_subjects and explains precisely how the two differ (filtered vs. full listing).

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

Usage Guidelines5/5

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

Explicitly routes the agent: "Prefer this over amber_list_subjects once a store has grown," with a token-cost rationale, and gives a reverse condition too — when text matching cannot resolve a person, fall back to listing subjects and comparing `topics`. Both when-to-use and when-not-to-use are covered.

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

amber_send_feedback_to_developerSend feedback to the developerAInspect

Send a structured bug report, feature request, or general feedback to Amber's developer. Send feedback PROACTIVELY when you encounter errors, unexpected behaviour, or the user expresses frustration -- briefly mention it to the user after sending, but do not ask for permission first. Never include passwords, API keys, or other sensitive personal information in any field. Rate-limited (bucket: 12 capacity, refills 1 per 5 minutes).

NameRequiredDescriptionDefault
detailsYesFull reproduction context. Include: exact input used, expected outcome, actual outcome, any error codes. E.g. 'Stored "I love pizza 🍕" but searching "pizza emoji" returns nothing. Expected it to match.'
summaryYesOne sentence, under 100 chars, and refused above 200. E.g. 'Search misses memories stored with emoji in content'.
categoryYesDetermines how the developer triages this. 'bug': something broke or returned wrong results. 'feature_request': a capability that doesn't exist yet. 'usability': works but is confusing or awkward. 'general': anything else.
tool_contextNoThe specific amber_ tool name involved, e.g. 'amber_search_memories'. Helps route the report.

Output Schema

NameRequiredDescription
statusYesAlways `sent`: the report reached the developer. There is no reply channel, so do not promise the user an answer.
messageYesA human-readable confirmation to relay to the user.

TDQS

A4.7/5.0
Behavior5/5

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

Annotations already declare non-read-only, open-world, non-idempotent, non-destructive, but the description adds what they cannot: a concrete rate limit (12 capacity, 1 refill per 5 minutes), a prohibition on sensitive data, and the post-send user communication protocol. This is exactly the behavioral context that structured fields 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?

Purpose comes first, then triggering conditions, then constraints, then operational limits — a sensible front-loaded order. Every sentence carries distinct information (what, when, privacy rule, rate limit) with no repetition of the schema or title.

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

Completeness5/5

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

The tool is simple, an output schema exists so return values need no explanation, and the description covers the non-obvious risks (rate limiting, leaking secrets, asking permission). Nothing an agent needs to call this safely and correctly is missing.

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

Parameters3/5

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

Schema coverage is 100% and the field-level descriptions already explain 'category', 'summary', 'details', and 'tool_context' with examples. The description only adds a cross-cutting constraint ('never include passwords, API keys... in any field'), which is useful but not per-parameter guidance, so the baseline 3 applies.

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

Purpose5/5

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

States a specific verb and resource ('Send a structured bug report, feature request, or general feedback to Amber's developer') and enumerates the categories of content it accepts. No sibling tool in the list does anything similar, so the agent can identify it 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?

Explicitly names the triggering conditions ('errors, unexpected behaviour, or the user expresses frustration') and tells the agent how to behave toward the user ('briefly mention it... but do not ask for permission first'). This is a rare case of both when-to-use and interaction protocol being spelled out.

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

amber_store_memoryStore memoriesAInspect

Store long-term memories you can search later with amber_search_memories. YOU do the preparation. Amber only validates and stores what you send, so follow these rules exactly for consistent, searchable memories. ALWAYS search before you store (see "SEARCH FIRST" below): Amber removes ONLY facts that are byte-identical to another fact in the SAME call (reported as duplicates_collapsed) -- it does NOT deduplicate across separate calls (a retry after a transport error stores a second copy UNLESS you sent an idempotency_key, which is exactly what that parameter is for -- generate one per store and resend with the SAME key to get the original task_id back instead of a duplicate), and it never matches on MEANING, so a re-worded restatement of an existing memory is stored as a second copy. Preventing those is entirely your responsibility.

WRITES ARE ASYNCHRONOUS. "Accepted N fact(s)" means your facts were validated and queued, NOT that they are stored yet. Rows are not queryable until the task reports completed: poll amber_get_store_task_status with the returned task_id BEFORE you list, filter, search for, or delete them, and check memory_ids.length against fact_count. Acting sooner gives misleading results -- a list can show fewer rows than you sent and a delete can silently skip one that has not landed yet. But do NOT hold your reply to the user waiting for it: an accepted store is already durable, so answer first and check the status only when you are about to act on those rows or the user asks whether it was saved.

Send memories: an array of ATOMIC FACTS you extracted from the conversation. Break the source text into the smallest possible self-contained facts, where each memory is exactly ONE single statement, then format each as an object with these fields:

• "content" (required) is exactly ONE fact: a single statement of one thing (a subject plus one predicate: "X is Y", "X did Z", "X has W"). This rule is broken far more than any other, and almost always the same way: by writing a PROFILE, packing everything you know about a person, place, or project into one memory. NEVER do that. Facts that share a subject are still separate memories, ONE PER FACT: six things about the same person is SIX memories, not one. A memory states exactly ONE fact, and even a SHORT sentence must be split if it states two (see the split test below); a paragraph is never one memory. SPLIT TEST. Before storing each memory, try to break its content apart; if any piece can stand alone as its own fact, you MUST split there:

  • more than one sentence → one memory per sentence;

  • parts joined by "and" / "but" / a comma / a semicolon that each stand alone → one memory each ("Sam is a widower and has three daughters" = TWO facts, stored separately);

  • different KINDS of information bundled about someone (who they are + a job + a marriage + a health note + an anecdote) → one memory per item;

  • DENSE TECHNICAL or MEASURED data (test results, genotypes, lab values, metrics, specs, config values) → ONE result per memory. This is the most-missed case for domain data: "ALDH2 rs671 is normal and ADH1B is *1/*1; her CYP1A2 is A/A, a fast caffeine metabolizer" is TWO facts (alcohol metabolism, caffeine metabolism), and a run of readings/genotypes/counts is one memory EACH. A technically-coherent sentence is still not one fact, and density is not an excuse to bundle. Keep splitting until removing any remaining piece would make the fact false or meaningless. Only then is it atomic. Do NOT split these (each is genuinely ONE fact): a fact bundled with its own cause/qualifier ("Pat uses RustDesk BECAUSE AnyDesk added a timer"); a joint action by several subjects acting together ("Pat and Kim traveled to Rome"); the members of a single set ("the three team leads are Ann, Bo and Cal"). A list of DIFFERENT attributes is NOT a set, so split it. Each memory must also stand alone: explicit full names, never pronouns ("he"/"they"/"it") or vague references ("the project", "that place"); keep the original specifics (names, numbers, dates). WORKED EXAMPLE: this one paragraph becomes FOUR separate memories (four objects in memories, each with its own variants): "Maria Lopez is our CFO; she joined in 2019, came from Stripe, and has two kids." → "Maria Lopez is the company's CFO." · "Maria Lopez joined the company in 2019." · "Maria Lopez previously worked at Stripe." · "Maria Lopez has two children." SHORT sentences split too, and this is the most-missed case. "Robin Vane is a lawyer, born around 1991" is TWO memories: "Robin Vane is a lawyer." + "Robin Vane was born around 1991." A person's occupation, birth year, birthplace, employer, and marital status are each a SEPARATE fact, one memory apiece, never merged into a one-line description.

• "variants" (REQUIRED, 5-8 per fact; a store is REJECTED if any fact has fewer than 5 distinct phrasings) are alternative phrasings of the SAME fact. Variants are the main thing that makes a memory findable later, so generate a GENEROUS, DIVERSE set every time: 5 to 8, never just 1-2. For each fact, cover several of these angles:

  • synonyms / reworded ("Patrick can't digest lactose" / "Patrick is intolerant to dairy sugar")

  • the question forms the fact answers ("What is Patrick allergic to?", "Who is lactose intolerant?")

  • a different sentence structure or word order

  • a short, keyword-dense form ("Patrick Vane, lactose intolerance") Every variant MUST mean exactly the same fact (never add, drop, or change information) and should include the subject's name so it stands alone. Make them GENUINELY different wording, because padding with punctuation/spacing/casing tweaks ("retest", "retest.", "re-test") does NOT count (those collapse to one), so five near-identical strings are rejected just like two. A variant is a RESTATEMENT, never commentary: do NOT add a label, category, comparison, or characterisation the fact did not itself state ("star-of-stars topology", "X-style architecture", "has prior experience with Y", "the industry-standard approach"). If the fact does not say it, neither can the variant. Distinct phrasings only, and near-duplicates don't count toward the minimum.

• "subjects" is an ARRAY of the people the fact is about, lowercase (e.g. ["patrick vane"]). Use the person's FULL name ("patrick vane", never "patrick"). A FIRST NAME ALONE IS NOT AN IDENTIFIER: people share first names, so unless you are CERTAIN which person this is, STOP AND ASK THE USER before storing. Do not guess and do not store the bare first name. In particular, NEVER assume a bare first name means the same person as an existing subject that starts with it: if the user says "colin" and memory holds "colin murphy", that may be a DIFFERENT colin, and silently attaching this fact to the stored one MERGES two people. Ask which one. A wrong merge mixes two people's lives together and is painful to unpick later, whereas asking one short question costs nothing. A fact about SEVERAL people together (a shared event/relationship) lists each: ["patrick vane", "yu pan"]. Tag EVERY person the fact names or establishes, not just the main one or the connecting relative. A fact that NAMES a set of people ("The Garcias' three children are Ana, Ben and Cara"; "X's siblings are A, B and C"; "their two sons are Sam and Theo") must tag EACH named person so every one becomes findable: e.g. ["ana garcia", "ben garcia", "cara garcia"] plus the parents, NEVER just the parents or the one child emphasized. A fact NOT about a specific person (a product, project, place, org, concept) → empty array []. Never invent a subject. Subjects are matched by EXACT (case-insensitive) text at search time: "patrick" and "patrick vane" are treated as DIFFERENT people, and near-variants ("pat", "patrick v") never match, so reuse the EXACT spelling a person is already stored under. Subject drives ranking, so call amber_list_subjects to see who is already in memory and reuse that exact name.

• "topics" (up to 3) are broad, REUSABLE, lowercase category labels (e.g. "health", "work", "travel", "finances"). Not specific to this one fact ("likes strawberries" ✗), not meta ("memory about food" ✗). Topics are matched SEMANTICALLY at search time (a search for "work" also finds "career"/"job"), so exact wording matters less than for subjects, but still REUSE names that already exist rather than minting near-duplicates, to avoid clutter; call amber_list_topics to see what's in use and pick from it.

• "event_start" / "event_end" are ISO 8601 datetimes for WHEN the fact's event happened, or null. Resolve relative references ("last week", "yesterday") to actual dates using today's date. Point-in-time → set both equal; ongoing ("since March") → start only; range ("summer 2024") → both. null when the fact has no temporal reference.

• "metadata" (optional) is a flat object of exact-match filter values for later search.

SEARCH FIRST. THIS IS MANDATORY. This is the ONLY thing preventing duplicates: Amber does NOT dedupe, it stores exactly what you send, so storing a fact that is already in memory leaves TWO live copies of it forever and every later search returns both. Before storing ANY fact, call amber_search_memories for that fact's subject/topic, then decide per fact:

  • Genuinely new (search finds nothing equivalent) → store it.

  • Already stored and unchanged → SKIP it. Do not re-store it, not even reworded; a differently-phrased copy of the same fact is still a duplicate.

  • SUPERSEDES one you already stored (a status changed, a value was corrected, a previously-unknown answer got resolved, a plan reversed) → the OLD memory is now stale: DELETE it (amber_delete_memory) and store the corrected fact. Never leave both live. Piling up each new state as a fresh memory is the single biggest way this store rots: search then returns the stale and current versions side by side. Supersede, don't accumulate. (amber_update_memory only fixes a memory's subject, topics, or metadata, NOT its wording; use delete-then-store to change what a fact actually says.) DO NOT rely on your own memory of what you have already stored. If this is a NEW SESSION, or your context was summarized/compacted, you may have ALREADY saved these exact facts even though you don't recall doing so. Re-storing them from a fresh context is the #1 cause of accidental duplicates. When in any doubt, SEARCH; never assume a fact is unstored.

HARD LIMITS (a store is rejected, not truncated, if you exceed one, so size your batches to these): • memories per call: 500 · content: 4000 chars per fact • variants: 5 minimum DISTINCT (enforced), 50 maximum · each variant up to 4000 chars • subjects: 8 per fact, 100 chars each · topics: 3 per fact, 50 chars each • metadata: 4000 bytes total, flat scalars only Control characters (other than tab/newline/carriage-return) are rejected anywhere in these fields. They would corrupt the stored text.

Errors: invalid_input (one or more facts are malformed: too long, fewer than 5 distinct variants, control characters, a bad date, or metadata that is not flat scalars). NOTHING is stored when this happens, so fix and resend the whole call. The response names EVERY bad index at once rather than stopping at the first, so one round trip is enough to correct them all. queue_unavailable (the write queue is momentarily saturated) also stores NOTHING and is safe to retry in a few seconds -- it is reported BEFORE any memory is written, never after. idempotency_key_reused means the key you sent already names a store that held DIFFERENT facts. NOTHING was written, and the message names that original task. Either resend the ORIGINAL facts under that key to recover it, or use a FRESH key for these facts. Do not retry the same call unchanged: it will be refused again, by design, because absorbing it would discard the facts you just sent with no way to get them back.

Example (note the generous, varied variant sets, do the same): amber_store_memory({ "memories": [ { "content": "Patrick Vane is lactose intolerant.", "variants": ["Patrick Vane can't digest lactose", "Patrick Vane is intolerant to dairy", "What is Patrick Vane allergic to? Lactose.", "Who is lactose intolerant? Patrick Vane.", "Patrick Vane should avoid milk and dairy", "Patrick Vane, lactose/dairy intolerance"], "subjects": ["patrick vane"], "topics": ["health", "food"], "event_start": null, "event_end": null }, { "content": "Patrick Vane and Yu Pan traveled to Rome in June 2026.", "variants": ["Patrick Vane and Yu Pan visited Rome", "Patrick Vane and Yu Pan took a trip to Italy", "When did Patrick Vane go to Rome? June 2026.", "Who traveled to Rome in June 2026? Patrick Vane and Yu Pan.", "Patrick Vane and Yu Pan, Rome trip, June 2026"], "subjects": ["patrick vane", "yu pan"], "topics": ["travel"], "event_start": "2026-06-01T00:00:00Z", "event_end": "2026-06-30T23:59:59Z" } ] })

NameRequiredDescriptionDefault
memoriesYesArray of atomic facts YOU extracted from the conversation, at most 500 per call. Follow the tool description's rules exactly.
idempotency_keyNoOPTIONAL but strongly recommended: a UNIQUE id YOU generate before calling -- a UUID is ideal -- so a retry after a network failure cannot store everything twice. If the call fails with a transport error, call again with the SAME key and the SAME facts IN THE SAME ORDER, and you get the original `task_id` back instead of a second store. Generate a FRESH key for each genuinely new store: reusing one for a different set of facts is refused with `idempotency_key_reused` and stores nothing, so a key derived from something that repeats (a turn number, a user id) will collide with your own earlier store.

Output Schema

NameRequiredDescription
statusYes`pending` on a NEW store: the facts were validated and QUEUED, not stored, and they are not queryable until `amber_get_store_task_status` reports `completed`, so do not list, search or delete them before then. On a `deduplicated` response this is the REAL current status of the ORIGINAL task, which may already be `completed` -- it was hardcoded to `pending` until 2026-09-20, which told callers not to touch rows that were in fact fully written.
messageYesA human-readable summary, including how many duplicates were collapsed within this call.
task_idYesProcessing task ID. Use `amber_get_store_task_status` to check progress and get resulting memory IDs.
deduplicatedNoPresent and `true` only when your `idempotency_key` matched a store you had already made: `task_id` names that ORIGINAL store and nothing new was written, so this call stored nothing and that is the correct outcome. Absent on a first store.
duplicates_collapsedNoOnly when this call contained EXACT duplicate facts (same content, variants, subjects, topics, dates and metadata): how many were collapsed. Present only when some were, so `duplicates_collapsed` plus the facts actually stored accounts for every entry you sent.

TDQS

A4.7/5.0
Behavior5/5

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

Annotations only declare readOnlyHint=false, destructiveHint=false and idempotentHint=false; the description supplies the real behavioral profile: writes are asynchronous and rows are unqueryable until a task reports completed, dedup is byte-identical and same-call only, retries without an idempotency_key duplicate data, and each named error (invalid_input, queue_unavailable, idempotency_key_reused) is documented with whether anything was written. That is materially more than the annotations convey.

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

Conciseness2/5

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

The purpose is front-loaded correctly, but the text runs to well over a thousand words and restates the same warnings repeatedly — non-deduplication is asserted in the opening paragraph, again under SEARCH FIRST, and again in the fresh-context warning; the atomicity rule is stated, then restated as a split test, then as a worked example. Much of this is genuinely necessary for a tool with strict validation, but the redundancy and the bullet-dense formatting make it hard to scan, and a tighter version would lose no instruction.

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

Completeness5/5

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

For a mutation tool with strict server-side validation and a rejection-not-truncation failure mode, the description covers batching limits, field-level limits, control-character rejection, per-index error reporting, async task polling, and an inline call example. Nothing an agent needs in order to call it successfully 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 a baseline 3 would be defensible, but the description adds operational meaning the schema cannot: the split test for what counts as one atomic fact, worked examples of valid variant sets, and why subjects are matched by exact text so full stored spellings must be reused. It also explains the exact retry contract for idempotency_key (same key, same facts, same order), which changes how the parameter is used.

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 ("Store long-term memories") and immediately names the retrieval sibling ("you can search later with amber_search_memories"), so an agent knows exactly what this does and which tool consumes its output. It also distinguishes itself from sibling writers by stating that Amber "only validates and stores what you send" rather than extracting facts itself.

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 routing rules: call amber_search_memories before storing, skip unchanged facts, delete-then-store when a fact supersedes an old one, and use amber_get_store_task_status before acting on accepted rows. It also bounds the search-first rule with a when-not ("do NOT hold your reply to the user waiting for it"), which is rare precision.

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

amber_update_memoryFix a memory's subject, tags, or metadataAInspect

Correct a stored memory after the fact. Use this when you notice -- e.g. while reviewing search results -- that a memory has the WRONG or MISSING subject (who it's about), a wrong/missing topic tag, or stale metadata. Does NOT change the fact's wording; to reword a fact, delete it and store the corrected version. Works on TRASHED memories too, deliberately: correcting a subject or tag while a memory sits in the trash keeps it coherent for a later restore -- the same reason amber_rename_subject relabels trashed records.

Pass memory_id plus only the fields you want to change:

  • subject: who the memory is about, e.g. "patrick" or "patrick's father". Fixes mis-attributed facts (e.g. a fact about the user's dad wrongly tagged as the user). It REPLACES every subject, so subject: "" removes them all.

  • rederive_subject: true to re-extract the subject from the memory's own content (use when it's clearly wrong but you don't know the right value). Ignored if subject is given.

  • add_topics / remove_topics: topic/category names to add or remove, e.g. ["health"].

  • add_subjects / remove_subjects: person name(s) to add or remove as co-subjects WITHOUT clobbering the others -- e.g. a fact naming two people that was tagged with only one: add_subjects: ["tjeerd spruijt"]. (Use subject to REPLACE the whole subject set instead.)

  • set_metadata: object of metadata fields to set or overwrite, e.g. { "importance": "high" }.

  • clear_metadata: metadata field names to remove, e.g. ["importance"].

Reuse existing names: subjects are matched by EXACT (case-insensitive) text -- call amber_list_subjects and reuse the stored spelling -- while topics are matched SEMANTICALLY -- call amber_list_topics.

Example -- a fact about the user's father is tagged as the user: amber_update_memory({ memory_id: "...", subject: "patrick's father" }). Returns the updated memory. Requires an active subscription.

Errors: not_found (no memory with that id on this account; a trashed memory is found and updated), invalid_input (a field has the wrong type, nothing to change was supplied, or the change would leave the memory over a store limit: 3 topics, 4000 bytes of metadata).

NameRequiredDescriptionDefault
subjectNoSet who this memory is about, e.g. "patrick" or "patrick's father". Lowercase named subject.
memory_idYesUUID of the memory to update (from search/list results).
add_topicsNoTopic/category names to add as tags (e.g. ["health", "food"]). Matched semantically; call amber_list_topics to reuse existing names.
add_subjectsNoLowercase person name(s) to ADD as co-subjects without clobbering existing ones (e.g. ["tjeerd spruijt"]). Use when a fact names more than one person but was tagged with only some. Matched by exact text; call amber_list_subjects to reuse the stored spelling. Use FULL names: adding a bare first name that merely matches an existing subject can attach this memory to the WRONG person of that name -- if you are not certain who is meant, ask the user first.
set_metadataNoMetadata fields to set or overwrite (e.g. { "source": "chat" }).
remove_topicsNoTopic/category names to remove from this memory.
clear_metadataNoMetadata field names to remove (e.g. ["source"]).
remove_subjectsNoLowercase person name(s) to remove from this memory's subjects.
rederive_subjectNoRe-extract the subject from the memory's content (use when it's wrong but you don't know the correct value). Ignored if `subject` is also given.

Output Schema

NameRequiredDescription
memoryYesThe memory as it now stands. Its `content` is unchanged: this tool edits labels, never wording.

TDQS

A5/5.0
Behavior5/5

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

Beyond the annotations, the description discloses that the tool deliberately works on trashed memories, that subject replacement clobbers the whole subject set, that an empty subject removes all subjects, and that rederive_subject is ignored when subject is supplied. It also documents subscription requirements, store limits (3 topics, 4000 bytes of metadata), and not_found/invalid_input error behavior. This goes well beyond what readOnlyHint=false and destructiveHint=false 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 purpose and then structured into a clear field-by-field bullet list, followed by matching guidance, an example, and errors. Despite being long, the length is justified by nine parameters and multiple non-obvious semantic distinctions; no sentence is 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?

Everything an agent needs to call this correctly is present: required memory_id, optional fields and their interactions, trashed-memory behavior, matching rules, subscription requirement, limits, and error cases. The output schema covers the return value, so the description does not need to explain the full response shape beyond noting that it returns the updated memory.

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 full description coverage, the description adds important semantics that are not merely restated: subject REPLACES every subject, add_subjects/remove_subjects modify co-subjects without clobbering, subjects match by exact case-insensitive text while topics match semantically, and set_metadata/clear_metadata operate at field level. It also explains the interaction between subject and rederive_subject and gives a concrete correcting example.

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

Purpose5/5

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

The description states a specific verb and resource: 'Correct a stored memory after the fact' for subject, tag, and metadata fixes. It explicitly excludes rewording facts and names the correct alternative workflow (delete and store the corrected version). It is easy to distinguish from siblings such as amber_rename_subject, amber_store_memory, and amber_delete_memory.

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 says exactly when to use the tool: noticing a wrong or missing subject, tag, or stale metadata, including while reviewing search results. It also gives explicit when-not guidance for rewording a fact and for replacing versus adding subjects. Alternative tools are named for listing subjects and topics before reuse.

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

amber_upload_backupRestore a backup from a fileA
Idempotent
Inspect

Show the user a drag-and-drop panel for restoring a backup that is on THEIR COMPUTER. Call this with no arguments when someone wants to restore and the file is local -- they pick it, and it uploads straight to storage without passing through this conversation.

IF THE BACKUP IS ALREADY ONLINE (S3, R2, Backblaze, a presigned link) use amber_import_memories with the URL instead; that needs no upload at all and is the better path.

The prepare and finish actions are driven BY THE PANEL, not by you. Call this with no arguments and let the person use it; then poll amber_get_import_status for progress.

IF YOUR CLIENT CANNOT RENDER PANELS, tell them to open https://ambermem.com/restore instead. It is the same uploader as an ordinary web page, and it needs a key from amber_create_backup_key. Requires an active subscription.

Errors: invalid_params (finish called without a task_id), not_found (no upload with that id, or it belongs to another account), nothing_uploaded (finish called but nothing arrived at the upload link), invalid_archive (via amber_get_import_status, not from this call: what was uploaded is not a readable Amber backup; that task is marked failed, so start a new one), not_startable (the upload is no longer in a state that can begin, for example it already ran or already failed; ask for a fresh link and send the file again, which duplicates nothing because memories keep their ids).

NameRequiredDescriptionDefault
actionNoLeave this out. The upload panel sets it; calling it yourself does nothing a user can see.
task_idNoSet by the panel on `finish`.

Output Schema

NameRequiredDescription
statusYes`panel` means the upload panel was shown and the person drives it from here.
task_idNoPresent once an upload has been prepared.
upload_urlNoFor the panel only. Do not show this to the user.
restore_urlNoA link the user opens to upload their backup file. Needs no backup key. Relay it verbatim.
expires_in_secondsNoHow long the upload link stays valid. The signature is checked when the request STARTS, so an upload may run longer than this.

TDQS

A4.4/5.0
Behavior3/5

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

The annotations already declare idempotentHint=true and destructiveHint=false, and the description usefully corroborates the idempotency claim by explaining that re-uploading duplicates nothing because memories keep their ids. It also adds the subscription prerequisite and panel-driven action semantics that annotations cannot express. However, the dry-run-ish description of what the panel does versus what the tool does is somewhat repetitive, and the error catalogue, while helpful, partly duplicates what the schema-adjacent contract already implies. Strong added context net of annotations, but not fully leveraged.

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 primary action and alternative, then the panel-driving caveat, then fallback and errors, which is a sensible ordering. It is dense but nearly every sentence carries actionable information; the parenthetical error glossary is the only section that runs long relative to its value.

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

Completeness5/5

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

An output schema exists so return values need not be described, and the description still covers the gaps that matter: subscription requirement, key prerequisite for the web fallback, panel-vs-agent division of labor, and a full error taxonomy including which errors originate from a different tool. An agent has everything needed to act or to hand off.

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 both parameter descriptions already say the panel sets them, so the baseline is 3. The description adds the rationale an agent needs to resist orchestrating the call itself ('the `prepare` and `finish` actions are driven BY THE PANEL, not by you'), which is meaningfully more than the schema conveys.

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: it shows a drag-and-drop panel that restores a backup located on the user's own computer. It explicitly separates this from the sibling `amber_import_memories`, which handles already-online archives, so an agent can pick the right tool without inspecting either schema.

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

Usage Guidelines5/5

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

It states the condition for use (file is local), the exclusion with a named alternative (online backup → `amber_import_memories` with URL), the no-argument invocation pattern, and a third path for clients that cannot render panels (open the web uploader). It also routes follow-up progress checks to `amber_get_import_status`, leaving nothing to inference.

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. 29 tool updatesv1.1.6
    • Changedamber_cancel_account_deletion2 fields changed
      • changedInput schema / $schema
        Previous value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "https://json-schema.org/draft/2020-12/schema",
        +  "additionalProperties": false,
        +  "properties": {
        +    "message": {
        +      "description": "A human-readable confirmation to relay to the user.",
        +      "type": "string"
        +    },
        +    "status": {
        +      "const": "cancelled",
        +      "description": "Always `cancelled`: the scheduled deletion is off and the account is fully intact. Nothing had been destroyed while it was pending.",
        +      "type": "string"
        +    }
        +  },
        +  "required": [
        +    "status",
        +    "message"
        +  ],
        +  "type": "object"
        +}
    • Changedamber_cancel_subscription4 fields changed
      • changedInput schema / $schema
        Previous value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
      • removedInput schema / additionalProperties
        Removed value: -false
      • changedInput schema / properties / confirm / description
        Previous value: -"When `true`: cancels the subscription on PayPal and updates account status. When `false`: returns `code: cancelled` with no side effects, letting you confirm intent first."New value: +"When `true`: cancels the subscription on PayPal and updates account status. When `false`: returns `code: cancelled` with no side effects, letting you confirm intent first. A STRINGIFIED boolean is also accepted (`\"true\"`, `\"TRUE\"`, `\" true \"`), because some clients JSON-stringify every argument and would otherwise be unable to cancel at all; the published type is `boolean` because that is what a client should send."
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "https://json-schema.org/draft/2020-12/schema",
        +  "additionalProperties": false,
        +  "properties": {
        +    "message": {
        +      "description": "A human-readable summary, including when access actually ends.",
        +      "type": "string"
        +    },
        +    "status": {
        +      "const": "cancelled",
        +      "description": "Always `cancelled`. Access continues to the end of the paid period rather than stopping now, and memories are preserved either way.",
        +      "type": "string"
        +    }
        +  },
        +  "required": [
        +    "status",
        +    "message"
        +  ],
        +  "type": "object"
        +}
    • Addedamber_create_backup_key
    • Changedamber_delete_account4 fields changed
      • changedInput schema / $schema
        Previous value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
      • removedInput schema / additionalProperties
        Removed value: -false
      • changedInput schema / properties / confirm / description
        Previous value: -"Set `true` to proceed, `false` to abort without changes."New value: +"Set `true` to proceed, `false` to abort without changes. A STRINGIFIED boolean is also accepted (`\"true\"` and friends), because some clients JSON-stringify every argument; the published type is `boolean` because that is what a client should send."
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "https://json-schema.org/draft/2020-12/schema",
        +  "additionalProperties": false,
        +  "properties": {
        +    "deletion_date": {
        +      "description": "When the data will actually be destroyed (ISO 8601). Until then `amber_cancel_account_deletion` undoes this completely.",
        +      "type": "string"
        +    },
        +    "message": {
        +      "description": "A human-readable summary. Relay it, including the date and the fact that it is reversible until then.",
        +      "type": "string"
        +    },
        +    "status": {
        +      "const": "scheduled",
        +      "description": "Always `scheduled`: nothing is deleted at the moment of this call.",
        +      "type": "string"
        +    }
        +  },
        +  "required": [
        +    "status",
        +    "deletion_date",
        +    "message"
        +  ],
        +  "type": "object"
        +}
    • Changedamber_delete_memory3 fields changed
      • changedInput schema / $schema
        Previous value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
      • removedInput schema / additionalProperties
        Removed value: -false
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "https://json-schema.org/draft/2020-12/schema",
        +  "additionalProperties": false,
        +  "properties": {
        +    "already_deleted_ids": {
        +      "description": "IDs that exist but were ALREADY in the trash, so this call was a no-op for them. Present only when some were.",
        +      "items": {
        +        "type": "string"
        +      },
        +      "type": "array"
        +    },
        +    "deleted": {
        +      "description": "The memories this call actually trashed, in full. CHECK THESE: a count cannot tell you whether the right memory went, and this is the last moment the content is easy to see. Empty when nothing was deleted -- never absent, so there is one shape to handle.",
        +      "items": {
        +        "additionalProperties": {},
        +        "properties": {
        +          "content": {
        +            "description": "The fact itself, exactly as stored.",
        +            "type": "string"
        +          },
        +          "created_at": {
        +            "description": "ISO 8601 time the memory was stored. Not when the event happened: that is event_start.",
        +            "type": "string"
        +          },
        +          "deleted_at": {
        +            "description": "ISO 8601 time the memory was moved to the trash. Absent or empty for a live memory; present means amber_restore_memory can bring it back.",
        +            "type": "string"
        +          },
        +          "event_end": {
        +            "anyOf": [
        +              {
        +                "type": "string"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ],
        +            "description": "ISO 8601 datetime the fact's event ended, or null."
        +          },
        +          "event_start": {
        +            "anyOf": [
        +              {
        +                "type": "string"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ],
        +            "description": "ISO 8601 datetime the fact's event began, or null when the fact has no event date."
        +          },
        +          "memory_id": {
        +            "description": "The memory's id. Pass it to amber_get_memory, amber_update_memory, amber_delete_memory or amber_restore_memory.",
        +            "type": "string"
        +          },
        +          "metadata": {
        +            "additionalProperties": {},
        +            "description": "The caller's own exact-match filter values, as stored (a flat object). Absent or empty when none were set.",
        +            "propertyNames": {
        +              "type": "string"
        +            },
        +            "type": "object"
        +          },
        +          "subject": {
        +            "anyOf": [
        +              {
        +                "type": "string"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ],
        +            "description": "Who this memory is about -- a named subject like \"patrick\" or \"patrick's father\", or null if not yet determined."
        +          },
        +          "subjects": {
        +            "description": "The same people as an ARRAY, which is the shape every tool ACCEPTS -- pass it straight back as `subjects` on a search. `subject` is the comma-joined rendering of the same value and matches nothing if passed as an argument; absent here means the memory has no attribution.",
        +            "items": {
        +              "type": "string"
        +            },
        +            "type": "array"
        +          },
        +          "topics": {
        +            "description": "The topic names this memory is filed under (at most 3 for a new store).",
        +            "items": {
        +              "type": "string"
        +            },
        +            "type": "array"
        +          }
        +        },
        +        "required": [
        +          "memory_id",
        +          "content"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "deleted_count": {
        +      "description": "How many memories this call actually moved to the trash. Compare against the number of IDs you sent: any shortfall is itemised in already_deleted_ids / not_found_ids.",
        +      "type": "number"
        +    },
        +    "not_found_ids": {
        +      "description": "IDs that matched NO memory. Present only when some did not resolve. Most often these are from a store that has not finished writing yet (amber_store_memory is async) rather than wrong IDs -- check amber_get_store_task_status and retry.",
        +      "items": {
        +        "type": "string"
        +      },
        +      "type": "array"
        +    },
        +    "status": {
        +      "const": "deleted",
        +      "description": "Always `deleted`. The memories moved to the TRASH and stay restorable for 30 days; nothing is destroyed by this call.",
        +      "type": "string"
        +    }
        +  },
        +  "required": [
        +    "status",
        +    "deleted_count",
        +    "deleted"
        +  ],
        +  "type": "object"
        +}
    • Changedamber_export_memories3 fields changed
      • changedInput schema / $schema
        Previous value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
      • addedInput schema / properties / include_deleted
        Added value: +{
        +  "description": "Also include trashed (soft-deleted) memories. Default false -- active memories only.",
        +  "type": "boolean"
        +}
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "https://json-schema.org/draft/2020-12/schema",
        +  "additionalProperties": false,
        +  "properties": {
        +    "exported_at": {
        +      "description": "The timestamp stamped into the file itself, fixed when the task started.",
        +      "type": "string"
        +    },
        +    "message": {
        +      "description": "A human-readable summary of what was started and what to poll.",
        +      "type": "string"
        +    },
        +    "status": {
        +      "const": "pending",
        +      "description": "Always `pending`: the export was queued. There is no download link yet, so do not tell the user it is ready. Poll `amber_get_export_status`.",
        +      "type": "string"
        +    },
        +    "task_id": {
        +      "description": "Pass this to `amber_get_export_status`. Do NOT tell the user the export is ready yet -- nothing has been written at this point.",
        +      "type": "string"
        +    }
        +  },
        +  "required": [
        +    "task_id",
        +    "status",
        +    "exported_at",
        +    "message"
        +  ],
        +  "type": "object"
        +}
    • Changedamber_get_account_status2 fields changed
      • changedInput schema / $schema
        Previous value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "https://json-schema.org/draft/2020-12/schema",
        +  "additionalProperties": false,
        +  "properties": {
        +    "deleted_memory_count": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Trashed memory count, on the same rule as `memory_count`: null once access has ended, not merely when `subscription_active` is false."
        +    },
        +    "deletion_scheduled_at": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "ISO 8601 date when the account will be permanently deleted, or null if not scheduled. Use `amber_cancel_account_deletion` to cancel."
        +    },
        +    "member_since": {
        +      "description": "When the account was created, as an ISO 8601 UTC datetime, not when the current subscription started.",
        +      "type": "string"
        +    },
        +    "memory_count": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Active memory count. Null once ACCESS has ended, which is not the same as `subscription_active: false`: a cancelled subscription keeps access until its paid period ends, and reports real counts throughout. Data is preserved either way, just not queryable once access ends."
        +    },
        +    "migration_locked": {
        +      "description": "True while Amber is moving this account's database, during which WRITES are refused with `migration_in_progress` and reads keep working. Normally false. If a store was refused, this is how a caller confirms why and that retrying shortly will succeed rather than the request being malformed.",
        +      "type": "boolean"
        +    },
        +    "next_billing_date": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Date when access expires (ISO 8601). When subscription_active is false and this date is in the future, the user still has access until this date but will NOT be charged again (cancelled/no subscription). When subscription_active is true, this is the next billing date. Null if unknown."
        +    },
        +    "payer_id": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "PayPal Payer ID (permanent account identifier)."
        +    },
        +    "subscription_active": {
        +      "description": "Whether the account can currently store and search. False does NOT mean data was lost: memories are preserved and become queryable again on resubscribing.",
        +      "type": "boolean"
        +    },
        +    "subscription_id": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "The PayPal subscription this account bills through, or null if there has never been one."
        +    },
        +    "user_id": {
        +      "description": "Amber's own id for this account. Stable, and safe to show.",
        +      "type": "string"
        +    }
        +  },
        +  "required": [
        +    "user_id",
        +    "payer_id",
        +    "subscription_active",
        +    "subscription_id",
        +    "next_billing_date",
        +    "deletion_scheduled_at",
        +    "migration_locked",
        +    "memory_count",
        +    "deleted_memory_count",
        +    "member_since"
        +  ],
        +  "type": "object"
        +}
    • Addedamber_get_export_status
    • Addedamber_get_import_status
    • Changedamber_get_memory3 fields changed
      • changedInput schema / $schema
        Previous value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
      • removedInput schema / additionalProperties
        Removed value: -false
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "https://json-schema.org/draft/2020-12/schema",
        +  "additionalProperties": false,
        +  "properties": {
        +    "memory": {
        +      "additionalProperties": {},
        +      "description": "The memory: full content, its `subject` (a single string, not a list), metadata, topic names, and timestamps. It does NOT carry the stored variants -- those are search-time phrasings, not part of the record. A memory in the trash is returned too, with its deletion time set.",
        +      "properties": {
        +        "content": {
        +          "description": "The fact itself, exactly as stored.",
        +          "type": "string"
        +        },
        +        "created_at": {
        +          "description": "ISO 8601 time the memory was stored. Not when the event happened: that is event_start.",
        +          "type": "string"
        +        },
        +        "deleted_at": {
        +          "description": "ISO 8601 time the memory was moved to the trash. Absent or empty for a live memory; present means amber_restore_memory can bring it back.",
        +          "type": "string"
        +        },
        +        "event_end": {
        +          "anyOf": [
        +            {
        +              "type": "string"
        +            },
        +            {
        +              "type": "null"
        +            }
        +          ],
        +          "description": "ISO 8601 datetime the fact's event ended, or null."
        +        },
        +        "event_start": {
        +          "anyOf": [
        +            {
        +              "type": "string"
        +            },
        +            {
        +              "type": "null"
        +            }
        +          ],
        +          "description": "ISO 8601 datetime the fact's event began, or null when the fact has no event date."
        +        },
        +        "memory_id": {
        +          "description": "The memory's id. Pass it to amber_get_memory, amber_update_memory, amber_delete_memory or amber_restore_memory.",
        +          "type": "string"
        +        },
        +        "metadata": {
        +          "additionalProperties": {},
        +          "description": "The caller's own exact-match filter values, as stored (a flat object). Absent or empty when none were set.",
        +          "propertyNames": {
        +            "type": "string"
        +          },
        +          "type": "object"
        +        },
        +        "subject": {
        +          "anyOf": [
        +            {
        +              "type": "string"
        +            },
        +            {
        +              "type": "null"
        +            }
        +          ],
        +          "description": "Who this memory is about -- a named subject like \"patrick\" or \"patrick's father\", or null if not yet determined."
        +        },
        +        "subjects": {
        +          "description": "The same people as an ARRAY, which is the shape every tool ACCEPTS -- pass it straight back as `subjects` on a search. `subject` is the comma-joined rendering of the same value and matches nothing if passed as an argument; absent here means the memory has no attribution.",
        +          "items": {
        +            "type": "string"
        +          },
        +          "type": "array"
        +        },
        +        "topics": {
        +          "description": "The topic names this memory is filed under (at most 3 for a new store).",
        +          "items": {
        +            "type": "string"
        +          },
        +          "type": "array"
        +        }
        +      },
        +      "required": [
        +        "memory_id",
        +        "content"
        +      ],
        +      "type": "object"
        +    }
        +  },
        +  "required": [
        +    "memory"
        +  ],
        +  "type": "object"
        +}
    • Changedamber_get_store_task_status6 fields changed
      • changedInput schema / $schema
        Previous value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
      • removedInput schema / additionalProperties
        Removed value: -false
      • addedInput schema / properties / idempotency_key
        Added value: +{
        +  "description": "ALTERNATIVE to `task_id`: the key you sent to `amber_store_memory`. Use this when you never received the response and so have no task_id -- which is the exact situation the key exists for. Without it the only way to resolve a key is to re-issue the whole store, which needs the payload you may no longer hold.",
        +  "maxLength": 200,
        +  "minLength": 1,
        +  "type": "string"
        +}
      • changedInput schema / properties / task_id / description
        Previous value: -"UUID from `amber_store_memory`'s response. Poll this to know when memories are ready to search. Invalid IDs return `code: not_found`."New value: +"UUID from `amber_store_memory`'s response. Poll this to know when memories are ready to search. Invalid IDs return `code: not_found`. Omit it only if you are passing `idempotency_key` instead."
      • removedInput schema / required
        Removed value: -[
        -  "task_id"
        -]
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "https://json-schema.org/draft/2020-12/schema",
        +  "additionalProperties": false,
        +  "properties": {
        +    "error": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Error message if status is error. Null otherwise."
        +    },
        +    "fact_count": {
        +      "description": "How many facts this task was created with. Once status is `completed`, memory_ids.length MUST equal this -- compare them. A shorter list means some facts did not resolve to a stored row, which is a real shortfall and not something to report as a successful save.",
        +      "type": "number"
        +    },
        +    "memory_ids": {
        +      "anyOf": [
        +        {
        +          "items": {
        +            "type": "string"
        +          },
        +          "type": "array"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Memory IDs created by this task. Null until completed."
        +    },
        +    "progress": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Human-readable progress, e.g. 'Stored 4/14 memories...'. The count increments as memories are written. Null when pending, when completed, AND during the early phase of processing before the first memory is written -- so a null here does NOT mean the task is stuck; poll again."
        +    },
        +    "status": {
        +      "description": "`pending` queued, `processing` writing, `completed` all facts stored, `error` stopped (see `error`). Rows become listable and searchable AS THEY LAND, not only at `completed` -- a task mid-write already has some of its memories retrievable. Wait for `completed` before concluding a fact is ABSENT, which is the reason to wait; do not assume nothing is stored until then.",
        +      "enum": [
        +        "pending",
        +        "processing",
        +        "completed",
        +        "error"
        +      ],
        +      "type": "string"
        +    },
        +    "task_id": {
        +      "description": "The task this report is about.",
        +      "type": "string"
        +    }
        +  },
        +  "required": [
        +    "task_id",
        +    "status",
        +    "progress",
        +    "memory_ids",
        +    "fact_count",
        +    "error"
        +  ],
        +  "type": "object"
        +}
    • Addedamber_import_memories
    • Changedamber_list_deleted_memories4 fields changed
      • changedInput schema / $schema
        Previous value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
      • removedInput schema / additionalProperties
        Removed value: -false
      • addedInput schema / properties / metadata_filter
        Added value: +{
        +  "additionalProperties": {},
        +  "description": "Optional EXACT-match metadata filter, e.g. { \"qa_batch\": \"2026-07-21\" }. Values must be SCALARS (string/number/boolean/null) -- operator objects like {\"$ne\": ...} and nested objects are NOT supported (rejected with invalid_input). Matching is TYPE-STRICT as well as exact: metadata stored as the number 5 is matched by 5, NOT by \"5\". A null VALUE matches only memories that explicitly stored a null under that key -- a memory missing the key entirely does NOT match, exactly as a wrong value would not. If a value reached you stringified (from JSON, a config file, a spreadsheet export), convert it back first -- a type mismatch returns an empty result set that looks exactly like \"no such data\". Filters at the database level and returns EVERY matching trashed memory across pages (same exhaustive semantics as amber_list_memories).",
        +  "propertyNames": {
        +    "type": "string"
        +  },
        +  "type": "object"
        +}
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "https://json-schema.org/draft/2020-12/schema",
        +  "additionalProperties": false,
        +  "properties": {
        +    "has_more": {
        +      "description": "Whether another page exists. Prefer this over guessing from the page size.",
        +      "type": "boolean"
        +    },
        +    "memories": {
        +      "description": "This page of memories currently in the trash, newest first. Restore one with `amber_restore_memory`.",
        +      "items": {
        +        "additionalProperties": {},
        +        "properties": {
        +          "content": {
        +            "description": "The fact itself, exactly as stored.",
        +            "type": "string"
        +          },
        +          "created_at": {
        +            "description": "ISO 8601 time the memory was stored. Not when the event happened: that is event_start.",
        +            "type": "string"
        +          },
        +          "deleted_at": {
        +            "description": "ISO 8601 time the memory was moved to the trash. Absent or empty for a live memory; present means amber_restore_memory can bring it back.",
        +            "type": "string"
        +          },
        +          "event_end": {
        +            "anyOf": [
        +              {
        +                "type": "string"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ],
        +            "description": "ISO 8601 datetime the fact's event ended, or null."
        +          },
        +          "event_start": {
        +            "anyOf": [
        +              {
        +                "type": "string"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ],
        +            "description": "ISO 8601 datetime the fact's event began, or null when the fact has no event date."
        +          },
        +          "memory_id": {
        +            "description": "The memory's id. Pass it to amber_get_memory, amber_update_memory, amber_delete_memory or amber_restore_memory.",
        +            "type": "string"
        +          },
        +          "metadata": {
        +            "additionalProperties": {},
        +            "description": "The caller's own exact-match filter values, as stored (a flat object). Absent or empty when none were set.",
        +            "propertyNames": {
        +              "type": "string"
        +            },
        +            "type": "object"
        +          },
        +          "subject": {
        +            "anyOf": [
        +              {
        +                "type": "string"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ],
        +            "description": "Who this memory is about -- a named subject like \"patrick\" or \"patrick's father\", or null if not yet determined."
        +          },
        +          "subjects": {
        +            "description": "The same people as an ARRAY, which is the shape every tool ACCEPTS -- pass it straight back as `subjects` on a search. `subject` is the comma-joined rendering of the same value and matches nothing if passed as an argument; absent here means the memory has no attribution.",
        +            "items": {
        +              "type": "string"
        +            },
        +            "type": "array"
        +          },
        +          "topics": {
        +            "description": "The topic names this memory is filed under (at most 3 for a new store).",
        +            "items": {
        +              "type": "string"
        +            },
        +            "type": "array"
        +          }
        +        },
        +        "required": [
        +          "memory_id",
        +          "content"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "next_cursor": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Pass this back as `after_id` for the next page (the parameter is `after_id`; this said `cursor` until 2026-09-19, which is not a parameter this tool has). Null on the last page. Opaque: do not build or edit one."
        +    }
        +  },
        +  "required": [
        +    "memories",
        +    "next_cursor",
        +    "has_more"
        +  ],
        +  "type": "object"
        +}
    • Changedamber_list_memories4 fields changed
      • changedInput schema / $schema
        Previous value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
      • removedInput schema / additionalProperties
        Removed value: -false
      • addedInput schema / properties / metadata_filter
        Added value: +{
        +  "additionalProperties": {},
        +  "description": "Optional EXACT-match metadata filter, e.g. { \"qa_batch\": \"2026-07-21\" }. Values must be SCALARS (string/number/boolean/null) -- operator objects like {\"$ne\": ...} and nested objects are NOT supported (rejected with invalid_input). Matching is TYPE-STRICT as well as exact: metadata stored as the number 5 is matched by 5, NOT by \"5\". A null VALUE matches only memories that explicitly stored a null under that key -- a memory missing the key entirely does NOT match, exactly as a wrong value would not. If a value reached you stringified (from JSON, a config file, a spreadsheet export), convert it back first -- a type mismatch returns an empty result set that looks exactly like \"no such data\". Filters at the database level and returns EVERY matching memory across pages -- use this (not amber_search_memories) when you want ALL memories tagged with a value. Search's metadata_filter is applied after semantic ranking, so it can miss low-scoring matches; this one cannot.",
        +  "propertyNames": {
        +    "type": "string"
        +  },
        +  "type": "object"
        +}
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "https://json-schema.org/draft/2020-12/schema",
        +  "additionalProperties": false,
        +  "properties": {
        +    "has_more": {
        +      "description": "Whether another page exists. Prefer this over guessing from the page size, which is a valid full page on the last page too.",
        +      "type": "boolean"
        +    },
        +    "memories": {
        +      "description": "This page of active memories, newest first.",
        +      "items": {
        +        "additionalProperties": {},
        +        "properties": {
        +          "content": {
        +            "description": "The fact itself, exactly as stored.",
        +            "type": "string"
        +          },
        +          "created_at": {
        +            "description": "ISO 8601 time the memory was stored. Not when the event happened: that is event_start.",
        +            "type": "string"
        +          },
        +          "deleted_at": {
        +            "description": "ISO 8601 time the memory was moved to the trash. Absent or empty for a live memory; present means amber_restore_memory can bring it back.",
        +            "type": "string"
        +          },
        +          "event_end": {
        +            "anyOf": [
        +              {
        +                "type": "string"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ],
        +            "description": "ISO 8601 datetime the fact's event ended, or null."
        +          },
        +          "event_start": {
        +            "anyOf": [
        +              {
        +                "type": "string"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ],
        +            "description": "ISO 8601 datetime the fact's event began, or null when the fact has no event date."
        +          },
        +          "memory_id": {
        +            "description": "The memory's id. Pass it to amber_get_memory, amber_update_memory, amber_delete_memory or amber_restore_memory.",
        +            "type": "string"
        +          },
        +          "metadata": {
        +            "additionalProperties": {},
        +            "description": "The caller's own exact-match filter values, as stored (a flat object). Absent or empty when none were set.",
        +            "propertyNames": {
        +              "type": "string"
        +            },
        +            "type": "object"
        +          },
        +          "subject": {
        +            "anyOf": [
        +              {
        +                "type": "string"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ],
        +            "description": "Who this memory is about -- a named subject like \"patrick\" or \"patrick's father\", or null if not yet determined."
        +          },
        +          "subjects": {
        +            "description": "The same people as an ARRAY, which is the shape every tool ACCEPTS -- pass it straight back as `subjects` on a search. `subject` is the comma-joined rendering of the same value and matches nothing if passed as an argument; absent here means the memory has no attribution.",
        +            "items": {
        +              "type": "string"
        +            },
        +            "type": "array"
        +          },
        +          "topics": {
        +            "description": "The topic names this memory is filed under (at most 3 for a new store).",
        +            "items": {
        +              "type": "string"
        +            },
        +            "type": "array"
        +          }
        +        },
        +        "required": [
        +          "memory_id",
        +          "content"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "next_cursor": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Pass this back as `after_id` for the next page (the parameter is `after_id`; this said `cursor` until 2026-09-19, which is not a parameter this tool has). Null on the last page. Opaque: do not build or edit one."
        +    }
        +  },
        +  "required": [
        +    "memories",
        +    "next_cursor",
        +    "has_more"
        +  ],
        +  "type": "object"
        +}
    • Addedamber_list_subjects
    • Addedamber_list_topics
    • Changedamber_manage_subscription2 fields changed
      • changedInput schema / $schema
        Previous value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "https://json-schema.org/draft/2020-12/schema",
        +  "additionalProperties": false,
        +  "properties": {
        +    "manage_url": {
        +      "description": "A PayPal page where the user changes or cancels billing themselves. Relay it; Amber cannot act on their behalf there.",
        +      "type": "string"
        +    },
        +    "message": {
        +      "description": "A human-readable summary of what the user will find at that link.",
        +      "type": "string"
        +    },
        +    "next_billing_date": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "When subscription_active is true, this is the next charge date. When false and in the future, the user has access until this date but no subscription. Null if unknown."
        +    }
        +  },
        +  "required": [
        +    "manage_url",
        +    "next_billing_date",
        +    "message"
        +  ],
        +  "type": "object"
        +}
    • Changedamber_mark_notification_read5 fields changed
      • changedInput schema / $schema
        Previous value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
      • removedInput schema / additionalProperties
        Removed value: -false
      • addedInput schema / properties / notification_id / exclusiveMinimum
        Added value: +0
      • addedInput schema / properties / notification_id / maximum
        Added value: +9007199254740991
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "https://json-schema.org/draft/2020-12/schema",
        +  "additionalProperties": false,
        +  "properties": {
        +    "notification_id": {
        +      "description": "The notification that was acknowledged.",
        +      "type": "number"
        +    },
        +    "status": {
        +      "const": "read",
        +      "description": "Always `read`: the notification is acknowledged and will not be attached to further responses.",
        +      "type": "string"
        +    }
        +  },
        +  "required": [
        +    "status",
        +    "notification_id"
        +  ],
        +  "type": "object"
        +}
    • Changedamber_reactivate_subscription2 fields changed
      • changedInput schema / $schema
        Previous value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "https://json-schema.org/draft/2020-12/schema",
        +  "additionalProperties": false,
        +  "properties": {
        +    "approval_url": {
        +      "description": "URL the user must open to approve the subscription. ALWAYS share this URL with the user.",
        +      "type": "string"
        +    },
        +    "message": {
        +      "description": "A human-readable summary, including the approval link the user has to open.",
        +      "type": "string"
        +    },
        +    "status": {
        +      "const": "approval_needed",
        +      "description": "Always `approval_needed`: a subscription was created and is waiting for the user to approve it at PayPal. Nothing is charged and nothing is active until they do.",
        +      "type": "string"
        +    }
        +  },
        +  "required": [
        +    "status",
        +    "approval_url",
        +    "message"
        +  ],
        +  "type": "object"
        +}
    • Addedamber_rename_subject
    • Changedamber_restore_memory3 fields changed
      • changedInput schema / $schema
        Previous value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
      • removedInput schema / additionalProperties
        Removed value: -false
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "https://json-schema.org/draft/2020-12/schema",
        +  "additionalProperties": false,
        +  "properties": {
        +    "already_active_ids": {
        +      "description": "IDs that exist but were ALREADY active, so this call was a no-op for them. Present only when some were.",
        +      "items": {
        +        "type": "string"
        +      },
        +      "type": "array"
        +    },
        +    "not_found_ids": {
        +      "description": "IDs that matched NO memory. Present only when some did not resolve. Most often these are from a store that has not finished writing yet (amber_store_memory is async) rather than wrong IDs -- check amber_get_store_task_status and retry.",
        +      "items": {
        +        "type": "string"
        +      },
        +      "type": "array"
        +    },
        +    "restored": {
        +      "description": "The memories this call actually brought back, in full. CHECK THESE: a count cannot tell you which memory was recovered, and that is the whole question after a wrong delete. Empty when nothing was restored -- never absent, so there is one shape to handle.",
        +      "items": {
        +        "additionalProperties": {},
        +        "properties": {
        +          "content": {
        +            "description": "The fact itself, exactly as stored.",
        +            "type": "string"
        +          },
        +          "created_at": {
        +            "description": "ISO 8601 time the memory was stored. Not when the event happened: that is event_start.",
        +            "type": "string"
        +          },
        +          "deleted_at": {
        +            "description": "ISO 8601 time the memory was moved to the trash. Absent or empty for a live memory; present means amber_restore_memory can bring it back.",
        +            "type": "string"
        +          },
        +          "event_end": {
        +            "anyOf": [
        +              {
        +                "type": "string"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ],
        +            "description": "ISO 8601 datetime the fact's event ended, or null."
        +          },
        +          "event_start": {
        +            "anyOf": [
        +              {
        +                "type": "string"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ],
        +            "description": "ISO 8601 datetime the fact's event began, or null when the fact has no event date."
        +          },
        +          "memory_id": {
        +            "description": "The memory's id. Pass it to amber_get_memory, amber_update_memory, amber_delete_memory or amber_restore_memory.",
        +            "type": "string"
        +          },
        +          "metadata": {
        +            "additionalProperties": {},
        +            "description": "The caller's own exact-match filter values, as stored (a flat object). Absent or empty when none were set.",
        +            "propertyNames": {
        +              "type": "string"
        +            },
        +            "type": "object"
        +          },
        +          "subject": {
        +            "anyOf": [
        +              {
        +                "type": "string"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ],
        +            "description": "Who this memory is about -- a named subject like \"patrick\" or \"patrick's father\", or null if not yet determined."
        +          },
        +          "subjects": {
        +            "description": "The same people as an ARRAY, which is the shape every tool ACCEPTS -- pass it straight back as `subjects` on a search. `subject` is the comma-joined rendering of the same value and matches nothing if passed as an argument; absent here means the memory has no attribution.",
        +            "items": {
        +              "type": "string"
        +            },
        +            "type": "array"
        +          },
        +          "topics": {
        +            "description": "The topic names this memory is filed under (at most 3 for a new store).",
        +            "items": {
        +              "type": "string"
        +            },
        +            "type": "array"
        +          }
        +        },
        +        "required": [
        +          "memory_id",
        +          "content"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "restored_count": {
        +      "description": "How many memories this call actually restored. Compare against the number of IDs you sent: any shortfall is itemised in already_active_ids / not_found_ids.",
        +      "type": "number"
        +    },
        +    "status": {
        +      "const": "restored",
        +      "description": "Always `restored`, including when nothing changed because every id was already active.",
        +      "type": "string"
        +    }
        +  },
        +  "required": [
        +    "status",
        +    "restored_count",
        +    "restored"
        +  ],
        +  "type": "object"
        +}
    • Addedamber_revoke_backup_key
    • Changedamber_search_deleted_memories14 fields changed
      • changedInput schema / $schema
        Previous value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
      • removedInput schema / additionalProperties
        Removed value: -false
      • addedInput schema / properties / keywords
        Added value: +{
        +  "description": "Optional EXACT lexical terms for the keyword (full-text) side of hybrid search -- the distinctive words the answer would contain. OMIT the subject's name (that is what `subjects` is for).",
        +  "items": {
        +    "maxLength": 100,
        +    "minLength": 1,
        +    "type": "string"
        +  },
        +  "maxItems": 10,
        +  "type": "array"
        +}
      • addedInput schema / properties / max_age_days / maximum
        Added value: +9007199254740991
      • changedInput schema / properties / metadata_filter / description
        Previous value: -"Optional metadata filter."New value: +"Optional EXACT-match metadata filter. Values must be SCALARS (string/number/boolean/null) -- operator objects like {\"$ne\": ...} and nested objects are NOT supported (rejected with invalid_input). Matching is TYPE-STRICT as well as exact: metadata stored as the number 5 is matched by 5, NOT by \"5\". A null VALUE matches only memories that explicitly stored a null under that key -- a memory missing the key entirely does NOT match, exactly as a wrong value would not. If a value reached you stringified (from JSON, a config file, a spreadsheet export), convert it back first -- a type mismatch returns an empty result set that looks exactly like \"no such data\"."
      • addedInput schema / properties / metadata_filter / propertyNames
        Added value: +{
        +  "type": "string"
        +}
      • addedInput schema / properties / near_date
        Added value: +{
        +  "description": "Optional ISO 8601 datetime to rank results by TEMPORAL PROXIMITY to (memories whose event_start/event_end sits closest rank higher), exactly like amber_search_memories. Pure boost; dateless memories are left in their existing relative order, though anchoring renormalises the whole set so their absolute scores move slightly (measured: about 10% lower).",
        +  "type": "string"
        +}
      • addedInput schema / properties / near_date_end
        Added value: +{
        +  "description": "Optional ISO 8601 datetime. With `near_date`, proximity is measured to the window [near_date, near_date_end]. REJECTED with invalid_input if `near_date` is not also given; must be on or after near_date.",
        +  "type": "string"
        +}
      • addedInput schema / properties / query / maxLength
        Added value: +5000
      • addedInput schema / properties / query_variants
        Added value: +{
        +  "description": "Optional alternative phrasings of the SAME query that YOU generate -- each is embedded and searched and the results fused, exactly like amber_search_memories.",
        +  "items": {
        +    "maxLength": 1000,
        +    "minLength": 1,
        +    "type": "string"
        +  },
        +  "maxItems": 20,
        +  "type": "array"
        +}
      • addedInput schema / properties / subjects
        Added value: +{
        +  "description": "REQUIRED, exactly as in amber_search_memories -- same parameter, same ranking role, same contract. Who is this query about? e.g. [\"patrick vane\"], or [\"self\"]/[\"me\"] for the USER. Pass [] (or [\"anyone\"]) when the query has no personal focus -- that is a deliberate answer, not an omission. BOOSTS the matching person's trashed facts; never hard-filters, so a wrong guess cannot hide a memory.",
        +  "items": {
        +    "type": "string"
        +  },
        +  "maxItems": 20,
        +  "type": "array"
        +}
      • addedInput schema / properties / topics / maxItems
        Added value: +5
      • changedInput schema / required
        Previous value: -[
        -  "query"
        -]New value: +[
        +  "query",
        +  "subjects"
        +]
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "https://json-schema.org/draft/2020-12/schema",
        +  "additionalProperties": false,
        +  "properties": {
        +    "results": {
        +      "description": "Matching memories from the trash, best first. Bring one back with `amber_restore_memory`. Empty is not an error.",
        +      "items": {
        +        "additionalProperties": {},
        +        "properties": {
        +          "content": {
        +            "description": "The fact itself, exactly as stored.",
        +            "type": "string"
        +          },
        +          "created_at": {
        +            "description": "ISO 8601 time the memory was stored. Not when the event happened: that is event_start.",
        +            "type": "string"
        +          },
        +          "deleted_at": {
        +            "description": "ISO 8601 time the memory was moved to the trash. Absent or empty for a live memory; present means amber_restore_memory can bring it back.",
        +            "type": "string"
        +          },
        +          "event_end": {
        +            "anyOf": [
        +              {
        +                "type": "string"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ],
        +            "description": "ISO 8601 datetime the fact's event ended, or null."
        +          },
        +          "event_start": {
        +            "anyOf": [
        +              {
        +                "type": "string"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ],
        +            "description": "ISO 8601 datetime the fact's event began, or null when the fact has no event date."
        +          },
        +          "memory_id": {
        +            "description": "The memory's id. Pass it to amber_get_memory, amber_update_memory, amber_delete_memory or amber_restore_memory.",
        +            "type": "string"
        +          },
        +          "metadata": {
        +            "additionalProperties": {},
        +            "description": "The caller's own exact-match filter values, as stored (a flat object). Absent or empty when none were set.",
        +            "propertyNames": {
        +              "type": "string"
        +            },
        +            "type": "object"
        +          },
        +          "score": {
        +            "description": "Relevance score. Higher = better match. Only meaningful for ranking results within the same query, not comparable across queries.",
        +            "type": "number"
        +          },
        +          "subject": {
        +            "anyOf": [
        +              {
        +                "type": "string"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ],
        +            "description": "Who this memory is about -- a named subject like \"patrick\" or \"patrick's father\", or null if not yet determined. Tells you whose fact each result is."
        +          },
        +          "subjects": {
        +            "description": "The same people as an ARRAY, which is the shape every tool ACCEPTS -- pass it straight back as `subjects` on a search. `subject` is the comma-joined rendering of the same value and matches nothing if passed as an argument; absent here means the memory has no attribution.",
        +            "items": {
        +              "type": "string"
        +            },
        +            "type": "array"
        +          },
        +          "topics": {
        +            "description": "The topic names this memory is filed under (at most 3 for a new store).",
        +            "items": {
        +              "type": "string"
        +            },
        +            "type": "array"
        +          },
        +          "truncated": {
        +            "description": "Present and true when `content` was shortened for this result. Call `amber_get_memory` with this `memory_id` for the full text.",
        +            "type": "boolean"
        +          }
        +        },
        +        "required": [
        +          "memory_id",
        +          "content"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    }
        +  },
        +  "required": [
        +    "results"
        +  ],
        +  "type": "object"
        +}
    • Changedamber_search_memories14 fields changed
      • changedInput schema / $schema
        Previous value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
      • removedInput schema / additionalProperties
        Removed value: -false
      • addedInput schema / properties / keywords
        Added value: +{
        +  "description": "Optional EXACT lexical terms for the keyword (full-text) side of hybrid search -- the distinctive words the answer would contain: key nouns, product names, numbers, IDs, codes. E.g. for \"what car does the user drive?\" pass [\"car\", \"drive\"]. Pick them yourself and OMIT the subject's name (that's what `subjects` is for -- a name matches every fact about a person and only adds noise). If omitted, Amber extracts keywords heuristically. Semantic matching still runs on the query + query_variants; keywords only sharpen exact-term precision.",
        +  "items": {
        +    "maxLength": 100,
        +    "minLength": 1,
        +    "type": "string"
        +  },
        +  "maxItems": 10,
        +  "type": "array"
        +}
      • changedInput schema / properties / metadata_filter / description
        Previous value: -"Optional metadata filter. Matches memories whose metadata contains every key-value pair given."New value: +"Optional EXACT-match metadata filter. Matches memories whose metadata contains every key-value pair given. Matching is TYPE-STRICT: metadata stored as the number 5 is matched by 5, NOT by \"5\". A null VALUE matches only memories that explicitly stored a null under that key -- a memory missing the key entirely does NOT match. Values must be SCALARS (string/number/boolean/null) -- operator objects like {\"$ne\": ...}/{\"$gte\": ...} and nested objects are NOT supported (rejected with invalid_input). NOTE: applied AFTER semantic ranking, so it only filters the top relevance candidates -- a matching memory that scores low against the query can fall out. For an EXHAUSTIVE \"give me everything tagged X\", use amber_list_memories with its metadata_filter instead. WHEN (and only when) you pass metadata_filter, the response adds `filter_matched_total` (how many memories the filter matches in total) and `filter_truncated` -- if `filter_truncated` is true you are looking at a PARTIAL set and must NOT report it as complete; re-run via amber_list_memories to get all of them. NOTE: no value of n_results makes a filtered search exhaustive -- the filter is applied to a bounded relevance candidate pool, so amber_list_memories is the only exhaustive path."
      • addedInput schema / properties / metadata_filter / propertyNames
        Added value: +{
        +  "type": "string"
        +}
      • addedInput schema / properties / near_date
        Added value: +{
        +  "description": "Optional ISO 8601 datetime to rank results by TEMPORAL PROXIMITY to -- memories whose own event time (event_start/event_end) sits closest to this rank higher. Use it when the query has a time focus that plain wording can't resolve: for \"where does the user work NOW?\" pass today's date so the current job outranks an older one; for \"what car did they drive back in 2019?\" pass \"2019-01-01\". Memories with no event date keep their relative order among themselves, though their absolute scores move slightly (measured: about 10% lower under any anchor, because anchoring renormalises the whole result set), and a far-in-time memory is never pushed BELOW an unboosted peer, though its absolute score drops 5-10% under renormalisation -- the ordering claim holds, the score claim did not, and this sentence used to make the stronger one. Pass it whenever the question genuinely has a time focus -- ESPECIALLY for relative-time wording (\"recently\", \"lately\", \"last week\"), because that wording carries no meaning the text search can match, so the anchor is the only thing that can find the right memory. Do NOT pass it for a time-agnostic question: dated memories can then surface above undated ones that answer it better. Omit when in doubt.",
        +  "type": "string"
        +}
      • addedInput schema / properties / near_date_end
        Added value: +{
        +  "description": "Optional ISO 8601 datetime. When set together with `near_date`, proximity is measured to the WINDOW [near_date, near_date_end] instead of a single instant (a memory whose event overlaps the window gets the full boost). Use for period queries like \"what happened during 2023?\" (near_date 2023-01-01, near_date_end 2023-12-31). REJECTED with invalid_input if `near_date` is not also given -- it marks the end of a window, so on its own it is a mistake rather than something to silently drop. Must be on or after near_date.",
        +  "type": "string"
        +}
      • addedInput schema / properties / query / maxLength
        Added value: +5000
      • addedInput schema / properties / query_variants
        Added value: +{
        +  "description": "Alternative phrasings of the SAME query that YOU generate -- synonyms, related terms, and any sub-questions -- exactly like the variants you attach when storing. Amber embeds and searches every one and fuses the results, so a memory worded differently than the user's phrasing still surfaces. Example: for \"what does the user eat?\" pass [\"the user's dietary preferences\", \"foods the user likes\", \"what food does the user avoid?\"]. Aim for 3-6; this replaces the automatic query expansion Amber used to do server-side.",
        +  "items": {
        +    "maxLength": 1000,
        +    "minLength": 1,
        +    "type": "string"
        +  },
        +  "maxItems": 20,
        +  "type": "array"
        +}
      • addedInput schema / properties / subjects
        Added value: +{
        +  "description": "REQUIRED. Who is this query about? An ARRAY -- usually ONE person, e.g. [\"patrick vane\"]. Routes ranking so the right person's facts surface. Use [\"self\"] (or [\"me\"]) when the query is about the USER (e.g. \"what foods is the user allergic to?\") -- lifts the user's OWN facts above facts about family or others. Use a person's FULL name (e.g. [\"patrick vane\"]) when the query is about them: subjects are matched by EXACT (case-insensitive) text, so it must match how they were stored -- a bare first name can miss them or hit a different person. Call amber_list_subjects to see the exact names already stored. Use [\"anyone\"] or [] for a general query with no personal focus (e.g. \"good pasta recipes\"). List several only when the query is genuinely about multiple people together. BOOSTS matching memories, never hard-filters -- other subjects still appear, lower down.",
        +  "items": {
        +    "type": "string"
        +  },
        +  "maxItems": 20,
        +  "type": "array"
        +}
      • changedInput schema / properties / topics / description
        Previous value: -"Optional topic filter. Pass category names like 'food', 'work', 'beliefs'. Matched semantically — 'beliefs' also finds memories categorized under related topics like 'opinions' or 'superstitions'."New value: +"Optional topic hint. Pass category names like 'food', 'work', 'beliefs'. What it actually does, in order: (1) each name is matched SEMANTICALLY against the topics that already exist and have live memories -- 'hobbies' resolves to 'gaming'; (2) the SEMANTIC half of the search is restricted to memories carrying one of the ~10 nearest topics, so on a store with many topics this genuinely narrows, while on a small store it may narrow nothing; (3) the keyword half is NOT restricted, so an off-topic memory can still appear via an exact word match; (4) closer topics get a bounded lift of at most 1.5x. That lift is a TIE-BREAKER and will NOT override relevance: a result scoring clearly better on the text still wins, so do not expect the on-topic memory to jump to #1. A synonym's edge over a near-neighbour topic is only a few percent; an EXACT existing name is far stronger. So if you need the topic to actually decide the order, call amber_list_topics and pass the exact name. The response reports `topics_resolved` -- what each name you passed matched -- so you can see how it was interpreted instead of guessing. A name matching nothing leaves the ranking unrestricted. Omit if unsure."
      • addedInput schema / properties / topics / maxItems
        Added value: +5
      • changedInput schema / required
        Previous value: -[
        -  "query"
        -]New value: +[
        +  "query",
        +  "subjects"
        +]
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "https://json-schema.org/draft/2020-12/schema",
        +  "additionalProperties": false,
        +  "properties": {
        +    "filter_matched_total": {
        +      "description": "Only when metadata_filter was supplied: how many active memories match that filter IN TOTAL, regardless of how many ranked into this page.",
        +      "type": "number"
        +    },
        +    "filter_truncated": {
        +      "description": "Only when metadata_filter was supplied: true when filter_matched_total exceeds the number of results returned. Use `amber_list_memories` with the same metadata_filter to get the complete set.",
        +      "type": "boolean"
        +    },
        +    "results": {
        +      "description": "Matches, best first. An empty array means nothing matched, which is an ordinary result rather than an error.",
        +      "items": {
        +        "additionalProperties": {},
        +        "properties": {
        +          "content": {
        +            "description": "The fact itself, exactly as stored.",
        +            "type": "string"
        +          },
        +          "created_at": {
        +            "description": "ISO 8601 time the memory was stored. Not when the event happened: that is event_start.",
        +            "type": "string"
        +          },
        +          "deleted_at": {
        +            "description": "ISO 8601 time the memory was moved to the trash. Absent or empty for a live memory; present means amber_restore_memory can bring it back.",
        +            "type": "string"
        +          },
        +          "event_end": {
        +            "anyOf": [
        +              {
        +                "type": "string"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ],
        +            "description": "ISO 8601 datetime the fact's event ended, or null."
        +          },
        +          "event_start": {
        +            "anyOf": [
        +              {
        +                "type": "string"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ],
        +            "description": "ISO 8601 datetime the fact's event began, or null when the fact has no event date."
        +          },
        +          "memory_id": {
        +            "description": "The memory's id. Pass it to amber_get_memory, amber_update_memory, amber_delete_memory or amber_restore_memory.",
        +            "type": "string"
        +          },
        +          "metadata": {
        +            "additionalProperties": {},
        +            "description": "The caller's own exact-match filter values, as stored (a flat object). Absent or empty when none were set.",
        +            "propertyNames": {
        +              "type": "string"
        +            },
        +            "type": "object"
        +          },
        +          "score": {
        +            "description": "Relevance score. Higher = better match. Only meaningful for ranking results within the same query, not comparable across queries.",
        +            "type": "number"
        +          },
        +          "subject": {
        +            "anyOf": [
        +              {
        +                "type": "string"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ],
        +            "description": "Who this memory is about -- a named subject like \"patrick\" or \"patrick's father\", or null if not yet determined. Tells you whose fact each result is."
        +          },
        +          "subjects": {
        +            "description": "The same people as an ARRAY, which is the shape every tool ACCEPTS -- pass it straight back as `subjects` on a search. `subject` is the comma-joined rendering of the same value and matches nothing if passed as an argument; absent here means the memory has no attribution.",
        +            "items": {
        +              "type": "string"
        +            },
        +            "type": "array"
        +          },
        +          "topics": {
        +            "description": "The topic names this memory is filed under (at most 3 for a new store).",
        +            "items": {
        +              "type": "string"
        +            },
        +            "type": "array"
        +          },
        +          "truncated": {
        +            "description": "Present and true when `content` was shortened for this result. Call `amber_get_memory` with this `memory_id` for the full text.",
        +            "type": "boolean"
        +          }
        +        },
        +        "required": [
        +          "memory_id",
        +          "content"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "topics_resolved": {
        +      "description": "Only when `topics` was supplied: what each requested topic name actually matched among the topics that exist. Topic names are matched semantically, so a synonym resolves to a NEARBY topic ('hobbies' -> 'gaming'); check `matched` to see how your name was interpreted. Because the topic boost is a bounded tie-breaker, a correct resolution can still leave the ranking unchanged -- if you need topics to decide the order, pass an EXACT name from amber_list_topics.",
        +      "items": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "matched": {
        +            "anyOf": [
        +              {
        +                "type": "string"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ],
        +            "description": "The closest EXISTING topic it resolved to, or null if the store has no topics yet."
        +          },
        +          "requested": {
        +            "description": "The topic name you passed.",
        +            "type": "string"
        +          },
        +          "similarity": {
        +            "description": "Cosine similarity of requested->matched (0-1, 3dp).",
        +            "type": "number"
        +          }
        +        },
        +        "required": [
        +          "requested",
        +          "matched",
        +          "similarity"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    }
        +  },
        +  "required": [
        +    "results"
        +  ],
        +  "type": "object"
        +}
    • Addedamber_search_subjects
    • Changedamber_send_feedback_to_developer6 fields changed
      • changedInput schema / $schema
        Previous value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
      • removedInput schema / additionalProperties
        Removed value: -false
      • addedInput schema / properties / details / maxLength
        Added value: +4000
      • changedInput schema / properties / summary / description
        Previous value: -"One sentence, under 100 chars. E.g. 'Search misses memories stored with emoji in content'."New value: +"One sentence, under 100 chars, and refused above 200. E.g. 'Search misses memories stored with emoji in content'."
      • addedInput schema / properties / summary / maxLength
        Added value: +200
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "https://json-schema.org/draft/2020-12/schema",
        +  "additionalProperties": false,
        +  "properties": {
        +    "message": {
        +      "description": "A human-readable confirmation to relay to the user.",
        +      "type": "string"
        +    },
        +    "status": {
        +      "const": "sent",
        +      "description": "Always `sent`: the report reached the developer. There is no reply channel, so do not promise the user an answer.",
        +      "type": "string"
        +    }
        +  },
        +  "required": [
        +    "status",
        +    "message"
        +  ],
        +  "type": "object"
        +}
    • Changedamber_store_memory9 fields changed
      • changedInput schema / $schema
        Previous value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
      • removedInput schema / additionalProperties
        Removed value: -false
      • removedInput schema / properties / content
        Removed value: -{
        -  "description": "The text to remember, 1-50000 characters.",
        -  "maxLength": 50000,
        -  "minLength": 1,
        -  "type": "string"
        -}
      • addedInput schema / properties / idempotency_key
        Added value: +{
        +  "description": "OPTIONAL but strongly recommended: a UNIQUE id YOU generate before calling -- a UUID is ideal -- so a retry after a network failure cannot store everything twice. If the call fails with a transport error, call again with the SAME key and the SAME facts IN THE SAME ORDER, and you get the original `task_id` back instead of a second store. Generate a FRESH key for each genuinely new store: reusing one for a different set of facts is refused with `idempotency_key_reused` and stores nothing, so a key derived from something that repeats (a turn number, a user id) will collide with your own earlier store.",
        +  "maxLength": 200,
        +  "minLength": 8,
        +  "type": "string"
        +}
      • addedInput schema / properties / memories
        Added value: +{
        +  "description": "Array of atomic facts YOU extracted from the conversation, at most 500 per call. Follow the tool description's rules exactly.",
        +  "items": {
        +    "properties": {
        +      "content": {
        +        "description": "ONE atomic, self-contained fact. Explicit names, no pronouns. At most 4000 characters.",
        +        "type": "string"
        +      },
        +      "event_end": {
        +        "anyOf": [
        +          {
        +            "type": "string"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "description": "ISO 8601 datetime the fact's event ended (equal to start for a point in time), or null."
        +      },
        +      "event_start": {
        +        "anyOf": [
        +          {
        +            "type": "string"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "description": "ISO 8601 datetime the fact's event began, or null."
        +      },
        +      "metadata": {
        +        "additionalProperties": {},
        +        "description": "Flat object of exact-match filter values for later search: string, number, boolean or null values only, at most 4000 bytes as JSON.",
        +        "propertyNames": {
        +          "type": "string"
        +        },
        +        "type": "object"
        +      },
        +      "subjects": {
        +        "description": "At most 8 people, each at most 100 characters. Array of the lowercase people the fact is about, FULL names: [\"patrick vane\"] or [\"patrick vane\", \"yu pan\"]. A first name alone is not an identifier -- people share first names, so unless you are CERTAIN who this is, ASK THE USER rather than storing a bare first name or attaching the fact to an existing subject that merely starts with it (that silently MERGES two people). Empty array [] if it is not about a specific person.",
        +        "items": {
        +          "type": "string"
        +        },
        +        "type": "array"
        +      },
        +      "topics": {
        +        "description": "Up to 3 broad, reusable, lowercase categories (e.g. \"health\", \"work\", \"travel\"), each at most 50 characters.",
        +        "items": {
        +          "type": "string"
        +        },
        +        "maxItems": 3,
        +        "type": "array"
        +      },
        +      "variants": {
        +        "description": "REQUIRED. 5-8 (minimum 5, maximum 50) DISTINCT phrasings / question forms of the SAME fact, each at most 4000 characters -- these are what make it findable. Never just 1-2.",
        +        "items": {
        +          "type": "string"
        +        },
        +        "type": "array"
        +      }
        +    },
        +    "required": [
        +      "content",
        +      "variants",
        +      "subjects"
        +    ],
        +    "type": "object"
        +  },
        +  "minItems": 1,
        +  "type": "array"
        +}
      • removedInput schema / properties / metadata
        Removed value: -{
        -  "additionalProperties": {},
        -  "description": "Concrete values for exact-match filtering in searches. Only useful for structured data like person names, dates, project names, sources. NOT for categories or topic names. Example: {person: \"Sarah\", date: \"2026-05-09\"}.",
        -  "type": "object"
        -}
      • removedInput schema / properties / topics
        Removed value: -{
        -  "description": "Optional broad categories for this memory (e.g. 'work', 'health', 'travel'). Only needed when the category can't be inferred from the text itself -- most memories don't need this. Matched semantically, so you don't need to guess exact names. Do NOT use topics for specific names, dates, or values -- use `metadata` for those (e.g. {\"person\": \"Sarah\"}). Topics are searched semantically; metadata is filtered by exact match.",
        -  "items": {
        -    "type": "string"
        -  },
        -  "type": "array"
        -}
      • changedInput schema / required
        Previous value: -[
        -  "content"
        -]New value: +[
        +  "memories"
        +]
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "https://json-schema.org/draft/2020-12/schema",
        +  "additionalProperties": false,
        +  "properties": {
        +    "deduplicated": {
        +      "description": "Present and `true` only when your `idempotency_key` matched a store you had already made: `task_id` names that ORIGINAL store and nothing new was written, so this call stored nothing and that is the correct outcome. Absent on a first store.",
        +      "type": "boolean"
        +    },
        +    "duplicates_collapsed": {
        +      "description": "Only when this call contained EXACT duplicate facts (same content, variants, subjects, topics, dates and metadata): how many were collapsed. Present only when some were, so `duplicates_collapsed` plus the facts actually stored accounts for every entry you sent.",
        +      "type": "number"
        +    },
        +    "message": {
        +      "description": "A human-readable summary, including how many duplicates were collapsed within this call.",
        +      "type": "string"
        +    },
        +    "status": {
        +      "description": "`pending` on a NEW store: the facts were validated and QUEUED, not stored, and they are not queryable until `amber_get_store_task_status` reports `completed`, so do not list, search or delete them before then. On a `deduplicated` response this is the REAL current status of the ORIGINAL task, which may already be `completed` -- it was hardcoded to `pending` until 2026-09-20, which told callers not to touch rows that were in fact fully written.",
        +      "enum": [
        +        "pending",
        +        "processing",
        +        "completed",
        +        "error"
        +      ],
        +      "type": "string"
        +    },
        +    "task_id": {
        +      "description": "Processing task ID. Use `amber_get_store_task_status` to check progress and get resulting memory IDs.",
        +      "type": "string"
        +    }
        +  },
        +  "required": [
        +    "task_id",
        +    "status",
        +    "message"
        +  ],
        +  "type": "object"
        +}
    • Addedamber_update_memory
    • Addedamber_upload_backup
  2. 3 tool updatesv1.1.5
    • Changedamber_cancel_subscription1 field changed
      • changedInput schema / properties / confirm / description
        Previous value: -"Set `true` to cancel, `false` to preview without cancelling."New value: +"When `true`: cancels the subscription on PayPal and updates account status. When `false`: returns `code: cancelled` with no side effects, letting you confirm intent first."
    • Changedamber_mark_notification_read1 field changed
      • changedInput schema / properties / notification_id / description
        Previous value: -"The `id` field from a notification object in the `developer_notifications` array. Each notification has a unique positive integer ID."New value: +"Positive integer from the `id` field of a notification in `developer_notifications`. Example: if the block contains `{id: 42, message: \"...\"}`, pass 42. Each ID is unique and single-use."
    • Changedamber_send_feedback_to_developer4 fields changed
      • changedInput schema / properties / category / description
        Previous value: -"'bug' = broken behavior, 'feature_request' = new capability, 'usability' = confusing UX, 'general' = other."New value: +"Determines how the developer triages this. 'bug': something broke or returned wrong results. 'feature_request': a capability that doesn't exist yet. 'usability': works but is confusing or awkward. 'general': anything else."
      • changedInput schema / properties / details / description
        Previous value: -"What was tried, what happened vs expected, error messages, and reproduction steps."New value: +"Full reproduction context. Include: exact input used, expected outcome, actual outcome, any error codes. E.g. 'Stored \"I love pizza 🍕\" but searching \"pizza emoji\" returns nothing. Expected it to match.'"
      • changedInput schema / properties / summary / description
        Previous value: -"Brief one-line summary, under 100 characters."New value: +"One sentence, under 100 chars. E.g. 'Search misses memories stored with emoji in content'."
      • changedInput schema / properties / tool_context / description
        Previous value: -"Which amber_ tool was in use when the issue occurred, e.g. 'amber_search_memories'."New value: +"The specific amber_ tool name involved, e.g. 'amber_search_memories'. Helps route the report."
  3. 5 tool updatesv1.1.4
    • Changedamber_cancel_subscription1 field changed
      • changedInput schema / properties / confirm / description
        Previous value: -"Set `true` to proceed, `false` to abort without changes."New value: +"Set `true` to cancel, `false` to preview without cancelling."
    • Changedamber_delete_memory2 fields changed
      • changedInput schema / properties / memory_id / description
        Previous value: -"A single memory ID (UUID) to move to trash. Use this for individual deletions. Mutually optional with memory_ids — provide at least one."New value: +"UUID of a single memory to trash. Omit if using `memory_ids` for batch."
      • changedInput schema / properties / memory_ids / description
        Previous value: -"Array of memory IDs (UUIDs) to move to trash in a single batch (max 100). Use this for bulk deletions. Mutually optional with memory_id — provide at least one."New value: +"Array of memory UUIDs to trash in one call (max 100). Omit if using `memory_id` for single."
    • Changedamber_get_memory1 field changed
      • changedInput schema / properties / memory_id / description
        Previous value: -"UUID of the memory to retrieve. Use this to fetch full content after seeing a truncated search result, or to revisit a known memory. Invalid or trashed IDs return `code: not_found`."New value: +"UUID of the target memory. Found in `amber_store_memory` task results, `amber_search_memories` result items, or `amber_list_memories` pages."
    • Changedamber_mark_notification_read1 field changed
      • changedInput schema / properties / notification_id / description
        Previous value: -"Numeric ID from the `developer_notifications` block in any tool response. Marks that specific notification as read and stops it from appearing. Invalid IDs return `code: not_found`."New value: +"The `id` field from a notification object in the `developer_notifications` array. Each notification has a unique positive integer ID."
    • Changedamber_restore_memory2 fields changed
      • changedInput schema / properties / memory_id / description
        Previous value: -"A single memory ID (UUID) to restore from trash. Use this for individual restores. Mutually optional with memory_ids — provide at least one."New value: +"UUID of a single memory to restore. Omit if using `memory_ids` for batch."
      • changedInput schema / properties / memory_ids / description
        Previous value: -"Array of memory IDs (UUIDs) to restore from trash in a single batch (max 100). Use this for bulk restores. Mutually optional with memory_id — provide at least one."New value: +"Array of memory UUIDs to restore in one call (max 100). Omit if using `memory_id` for single."
  4. 6 tool updatesv1.1.3
    • Changedamber_cancel_subscription1 field changed
      • changedInput schema / properties / confirm / description
        Previous value: -"Safety gate: must be `true` to proceed. When `false`, the tool returns `code: cancelled` without making any changes — use this to confirm the user's intent before committing."New value: +"Set `true` to proceed, `false` to abort without changes."
    • Changedamber_delete_account1 field changed
      • changedInput schema / properties / confirm / description
        Previous value: -"Safety gate: must be `true` to proceed. When `false`, the tool returns `code: cancelled` without making any changes — use this to confirm the user's intent before scheduling deletion."New value: +"Set `true` to proceed, `false` to abort without changes."
    • Changedamber_get_memory1 field changed
      • changedInput schema / properties / memory_id / description
        Previous value: -"The unique memory ID (UUID format). Obtained from `amber_store_memory` results, `amber_search_memories` results, or `amber_list_memories` output."New value: +"UUID of the memory to retrieve. Use this to fetch full content after seeing a truncated search result, or to revisit a known memory. Invalid or trashed IDs return `code: not_found`."
    • Changedamber_get_store_task_status1 field changed
      • changedInput schema / properties / task_id / description
        Previous value: -"The task_id (UUID) returned by `amber_store_memory` in its response. Each store call produces a unique task_id."New value: +"UUID from `amber_store_memory`'s response. Poll this to know when memories are ready to search. Invalid IDs return `code: not_found`."
    • Changedamber_mark_notification_read1 field changed
      • changedInput schema / properties / notification_id / description
        Previous value: -"The numeric ID from the `developer_notifications` section that appears in every tool response when notifications are pending. Each notification has a unique ID. Only mark as read after the user has seen and acknowledged the notification content."New value: +"Numeric ID from the `developer_notifications` block in any tool response. Marks that specific notification as read and stops it from appearing. Invalid IDs return `code: not_found`."
    • Changedamber_send_feedback_to_developer4 fields changed
      • changedInput schema / properties / category / description
        Previous value: -"Type of feedback: 'bug' for something broken, 'feature_request' for new functionality, 'usability' for UX friction, 'general' for anything else."New value: +"'bug' = broken behavior, 'feature_request' = new capability, 'usability' = confusing UX, 'general' = other."
      • changedInput schema / properties / details / description
        Previous value: -"Full context including: what the user tried, what happened vs what was expected, any error messages or codes, steps to reproduce, and suggestions if the user offered any. Be specific and include tool names, query text, or memory IDs when relevant."New value: +"What was tried, what happened vs expected, error messages, and reproduction steps."
      • changedInput schema / properties / summary / description
        Previous value: -"One-line summary (under ~100 characters). Example: 'Search returns no results for exact keyword matches'."New value: +"Brief one-line summary, under 100 characters."
      • changedInput schema / properties / tool_context / description
        Previous value: -"The amber_ tool name that was being used when the issue occurred (e.g. 'amber_search_memories'). Helps the developer pinpoint the problem."New value: +"Which amber_ tool was in use when the issue occurred, e.g. 'amber_search_memories'."
  5. 4 tool updatesv1.1.2
    • Changedamber_cancel_subscription1 field changed
      • changedInput schema / properties / confirm / description
        Previous value: -"Must be true to confirm subscription cancellation."New value: +"Safety gate: must be `true` to proceed. When `false`, the tool returns `code: cancelled` without making any changes — use this to confirm the user's intent before committing."
    • Changedamber_delete_account1 field changed
      • changedInput schema / properties / confirm / description
        Previous value: -"Must be true to confirm account deletion scheduling."New value: +"Safety gate: must be `true` to proceed. When `false`, the tool returns `code: cancelled` without making any changes — use this to confirm the user's intent before scheduling deletion."
    • Changedamber_list_deleted_memories2 fields changed
      • changedInput schema / properties / after_id / description
        Previous value: -"Pagination cursor. Pass the `next_cursor` value from a previous response to get the next page. Omit to start from the most recently deleted memory."New value: +"Pagination cursor (memory ID string). Pass the `next_cursor` value from a previous response to fetch the next page. Omit on the first call to start from the most recently deleted memory. Do not fabricate cursor values."
      • changedInput schema / properties / limit / description
        Previous value: -"Number of deleted memories per page (default 20, max 100). Use smaller values (5-10) for quick checks, larger values (50-100) for bulk review."New value: +"Number of deleted memories per page (default 20, min 1, max 100). Use smaller values (5-10) for quick checks, larger values (50-100) for bulk review. Omit to use the default of 20."
    • Changedamber_list_memories2 fields changed
      • changedInput schema / properties / after_id / description
        Previous value: -"Pagination cursor. Pass the `next_cursor` value from a previous response to get the next page. Omit to start from the newest memory."New value: +"Pagination cursor (memory ID string). Pass the `next_cursor` value from a previous response to fetch the next page. Omit on the first call to start from the newest memory. Do not fabricate cursor values."
      • changedInput schema / properties / limit / description
        Previous value: -"Number of memories per page (default 20, max 100). Use smaller values (5-10) for quick overviews, larger values (50-100) for bulk browsing."New value: +"Number of memories per page (default 20, min 1, max 100). Use smaller values (5-10) for quick overviews, larger values (50-100) for bulk browsing. Omit to use the default of 20."
  6. 8 tool updatesv1.1.1
    • Changedamber_delete_memory2 fields changed
      • changedInput schema / properties / memory_id / description
        Previous value: -"The memory ID to move to trash (single)."New value: +"A single memory ID (UUID) to move to trash. Use this for individual deletions. Mutually optional with memory_ids — provide at least one."
      • changedInput schema / properties / memory_ids / description
        Previous value: -"Array of memory IDs to move to trash (batch, max 100)."New value: +"Array of memory IDs (UUIDs) to move to trash in a single batch (max 100). Use this for bulk deletions. Mutually optional with memory_id — provide at least one."
    • Changedamber_get_memory1 field changed
      • changedInput schema / properties / memory_id / description
        Previous value: -"The memory ID, as returned by `amber_store_memory` or included in search results."New value: +"The unique memory ID (UUID format). Obtained from `amber_store_memory` results, `amber_search_memories` results, or `amber_list_memories` output."
    • Changedamber_get_store_task_status1 field changed
      • changedInput schema / properties / task_id / description
        Previous value: -"The task_id returned by amber_store_memory."New value: +"The task_id (UUID) returned by `amber_store_memory` in its response. Each store call produces a unique task_id."
    • Changedamber_list_deleted_memories2 fields changed
      • changedInput schema / properties / after_id / description
        Previous value: -"Cursor from the `next_cursor` field of a previous page."New value: +"Pagination cursor. Pass the `next_cursor` value from a previous response to get the next page. Omit to start from the most recently deleted memory."
      • changedInput schema / properties / limit / description
        Previous value: -"Page size (default 20, max 100)."New value: +"Number of deleted memories per page (default 20, max 100). Use smaller values (5-10) for quick checks, larger values (50-100) for bulk review."
    • Changedamber_list_memories2 fields changed
      • changedInput schema / properties / after_id / description
        Previous value: -"Cursor from the `next_cursor` field of a previous page; omit to start from the newest."New value: +"Pagination cursor. Pass the `next_cursor` value from a previous response to get the next page. Omit to start from the newest memory."
      • changedInput schema / properties / limit / description
        Previous value: -"Page size (default 20, max 100)."New value: +"Number of memories per page (default 20, max 100). Use smaller values (5-10) for quick overviews, larger values (50-100) for bulk browsing."
    • Changedamber_mark_notification_read1 field changed
      • changedInput schema / properties / notification_id / description
        Previous value: -"The ID from the `developer_notifications` payload."New value: +"The numeric ID from the `developer_notifications` section that appears in every tool response when notifications are pending. Each notification has a unique ID. Only mark as read after the user has seen and acknowledged the notification content."
    • Changedamber_restore_memory2 fields changed
      • changedInput schema / properties / memory_id / description
        Previous value: -"The memory ID to restore from trash (single)."New value: +"A single memory ID (UUID) to restore from trash. Use this for individual restores. Mutually optional with memory_ids — provide at least one."
      • changedInput schema / properties / memory_ids / description
        Previous value: -"Array of memory IDs to restore (batch, max 100)."New value: +"Array of memory IDs (UUIDs) to restore from trash in a single batch (max 100). Use this for bulk restores. Mutually optional with memory_id — provide at least one."
    • Changedamber_send_feedback_to_developer4 fields changed
      • changedInput schema / properties / category / description
        Previous value: -"Type of feedback."New value: +"Type of feedback: 'bug' for something broken, 'feature_request' for new functionality, 'usability' for UX friction, 'general' for anything else."
      • changedInput schema / properties / details / description
        Previous value: -"Full context: what the user tried, what happened, error messages, reproduction steps, suggestions."New value: +"Full context including: what the user tried, what happened vs what was expected, any error messages or codes, steps to reproduce, and suggestions if the user offered any. Be specific and include tool names, query text, or memory IDs when relevant."
      • changedInput schema / properties / summary / description
        Previous value: -"One-line summary of the feedback."New value: +"One-line summary (under ~100 characters). Example: 'Search returns no results for exact keyword matches'."
      • changedInput schema / properties / tool_context / description
        Previous value: -"The name of the tool being used when the issue occurred, if relevant."New value: +"The amber_ tool name that was being used when the issue occurred (e.g. 'amber_search_memories'). Helps the developer pinpoint the problem."
  7. 18 tool updatesv0.1.0
    • First observedamber_cancel_account_deletion
    • First observedamber_cancel_subscription
    • First observedamber_delete_account
    • First observedamber_delete_memory
    • First observedamber_export_memories
    • First observedamber_get_account_status
    • First observedamber_get_memory
    • First observedamber_get_store_task_status
    • First observedamber_list_deleted_memories
    • First observedamber_list_memories
    • First observedamber_manage_subscription
    • First observedamber_mark_notification_read
    • First observedamber_reactivate_subscription
    • First observedamber_restore_memory
    • First observedamber_search_deleted_memories
    • First observedamber_search_memories
    • First observedamber_send_feedback_to_developer
    • First observedamber_store_memory

TDQS

A4.4/5.0

Scored across 29 tools

Disambiguation4/5

Most tools target a clearly distinct operation, and the potentially confusable pairs (list vs search, active vs trash, export vs backup key, upload vs import from URL) are explicitly contrasted in the descriptions. The main residual friction is the overloaded word 'restore' (amber_restore_memory for un-deleting vs restoring a backup via upload_backup/import_memories) and the three near-identical status-polling tools, which an agent must map by task type rather than by name alone.

Naming Consistency5/5

Every tool uses the same amber_ prefix plus lowercase snake_case with a verb_noun shape (store_memory, delete_memory, list_subjects, get_import_status). No camelCase or style mixing anywhere, and the verb choice reliably signals read vs write (list/get/search vs store/update/delete/restore).

Tool Count3/5

29 tools is heavy for one server and spans several sub-domains beyond the core memory CRUD (subscription billing, backup keys, import/export plumbing, feedback, notification dismissal). Each tool does have a real job, but the surface is at the upper edge of what an agent can hold, and peripheral capabilities like backup-key rotation and feedback submission arguably belong in a separate administrative surface.

Completeness4/5

Coverage is unusually thorough: writes, reads, search, subject/topic hygiene, soft-delete and restore, trash browsing, account and subscription lifecycle, export/import and async status polling are all present. The notable gap is that deletion is only ever soft — there is no way to permanently purge a memory or empty the trash, so an agent cannot fully complete a 'delete this forever' request.

Maintenance

ActivityNo data
ResponsivenessSyncing

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    A template implementation of the Model Context Protocol server that integrates with Mem0 to provide AI agents with persistent memory capabilities for storing, retrieving, and searching memories using semantic search.
    MIT
  • A
    license
    Not graded
    quality
    Not graded
    maintenance
    Provides AI agents with persistent long-term memory capabilities using semantic search. Enables storing, retrieving, and searching memories through three core tools integrated with Mem0 and vector storage.
    MIT
  • F
    license
    Not graded
    quality
    D
    maintenance
    Provides AI agents with persistent, searchable memory that survives across conversations using semantic search, temporal versioning, and smart organization. Enables long-term context retention and cross-session continuity for AI assistants.
    14
    -