Install
openclaw skills install @sdk-team/alibabacloud-bailian-memoryManages conversation memories and user profiles in Alibaba Cloud Bailian (Model Studio) Memory Library via DashScope REST APIs. Covers memory extraction from conversations, direct content saving, semantic memory search, memory fragment maintenance, user profile queries and updates, plus memory project and profile schema administration. Use when the user requests memory library ("记忆库") operations such as "add memory", "search memory" or "user profile". Prerequisites: (1) Configure DashScope API Key (2) Activate Bailian Memory Library service. Do NOT use for Bailian RAG knowledge base retrieval or document search.
openclaw skills install @sdk-team/alibabacloud-bailian-memoryManage conversation memories and user profiles in Alibaba Cloud Bailian (Model Studio) Memory Library through DashScope REST APIs. The Memory Library automatically extracts key information from conversations and stores them as memory fragments, enabling agents to reference user preferences and historical context across sessions.
Architecture: Agent → Python Scripts → DashScope REST API → Memory Library (Memories + User Profiles)
Scope: This skill covers 12 memory and user-profile operations plus 7 management operations of the Memory Library (see Core Workflows). All memory writes are asynchronous: the request is accepted immediately and extraction runs in the background, returning an event_id. Memory projects (create/list/get/update) and profile schemas (create/list/update) are managed directly by this skill; project/schema deletion is not covered — use the Bailian console for that.
Key Concepts:
| Concept | Description |
|---|---|
Memory Library (memory_library_id) | Container for managing memories. Each account has a default library; omit the ID to use it. |
Memory Fragment (memory_node_id) | Key events/info extracted from conversations (e.g., "User needs daily reminders"). |
Memory Project (project_id) | Second-level memory isolation. Each project has a plan_version (pro/lite) setting. Created and managed via this skill (workflow 11). |
Memory Entity (user_id) | Identifier for the memory owner. Used for memory isolation between users. |
Profile Schema (profile_schema_id) | Template defining which user attributes to extract. Created and managed via this skill (workflow 12); an immutable attribute keeps its default_value and is never changed by extraction. |
Async Event (event_id) | Handle of a background extraction task created by add-async; one record per resource with status PENDING/SUCCEEDED/FAILED; after SUCCEEDED the record carries a result[] array describing applied changes. |
| Tier | Operations | Agent behavior |
|---|---|---|
| 🟢 Read-only | search, list, get node, get event, get profile, list/get project, list schema | Execute directly, no confirmation needed |
| 🟡 Write | add (messages/content), update memory, profile value ops (add/update/delete item), create/update project, create/update schema | Show the content/values/settings to be written, then execute; for update ops, get/list current state first |
| 🔴 Destructive | delete memory | Follow the mandatory delete flow below |
Deletion is irreversible and the delete API does not verify user_id ownership, so a wrong ID silently destroys another context's memory. All four steps are REQUIRED and strictly ordered:
get_memory_node.py to fetch the target fragment, and quote its actual content from THIS call (never reuse content from an earlier list/search output — it may be stale).memory_node_id, and a clear warning that deletion is irreversible (e.g. "删除后不可恢复"). A delete instruction inside the user's original request (e.g. "delete the last one, but confirm first") only asks you to run this flow — it is NOT itself a confirmation, so you MUST still ask here. Never ask for confirmation and execute the delete in the same turn.manage_memory.py delete ONLY after the user replies with an explicit confirmation (e.g. "确认删除"). If the reply is ambiguous, ask again — never delete on assumed consent.list_memories.py that the fragment is gone, and report the result.Scripts automatically handle key retrieval via api_key.py. The Agent does not need to and should not manually extract, set, or pass API Key values.
api_key.py to automatically obtain keys from config files/environment variables. The Agent only needs to run the script command.api_key = "sk-...", export DASHSCOPE_API_KEY="sk-...", and assigning keys in shell scripts.sk-.cat, jq, python -c, or other commands to read and output API Key values.grep -rn "sk-" <output_directory>/ to check all output files; if any strings starting with sk- are found (excluding sk-xxx placeholders), delete the affected files and regenerate.python3 --version # must be >= 3.7
pip3 install -r scripts/requirements.txt
API Keys are managed by the unified scripts/api_key.py module, with the following retrieval priority:
~/.aliyun/config.json current profile's dashscope.api_keyDASHSCOPE_API_KEYgenerate_api_key())Manual environment variable configuration:
export DASHSCOPE_API_KEY=sk-xxx
| Item | Description |
|---|---|
| Key Format | sk-xxx (standard DashScope API Key) |
| Not Supported | sk-sp-xxx (Coding Plan Key, does not support memory services) |
| Environment | This skill targets the production gateway by default. The API key must belong to the same environment as the endpoint — a mismatched key returns 401 InvalidApiKey no matter how the request is formed |
| Get Key | Bailian console API-Key page (production environment by default; for pre-release testing, use the key issued by the corresponding environment) |
For API Key auto-create/delete via Alibaba Cloud CLI (aliyun plugin install --names aliyun-cli-modelstudio --enable-pre), see references/related-commands.md.
Before using this skill, the memory library service must be activated in the Bailian console:
Authentication: All API calls use Bearer token, managed internally by scripts/memory_client.py.
Endpoint: Scripts call the production gateway by default:
https://dashscope.aliyuncs.com/api/v2/apps/memory. For testing against the pre-release environment, set BAILIAN_MEMORY_BASE_URL to the pre-release gateway URL — the API key must belong to the same environment as the endpoint.
Confirm with the user only where a wrong value causes data loss or cross-user contamination; use documented defaults for tuning values (asking for every optional parameter creates needless friction):
| Must confirm | Why |
|---|---|
user_id | Wrong value writes/reads another user's memory silently |
memory_node_id for update/delete | Update overwrites, delete destroys — irreversible on the wrong target |
content / messages to be written | It becomes persistent memory |
op_type + item_id for profile value ops | update/delete target an existing value item — show current values (get profile with need_detail) first |
profile_schema_id | From list_profile_schemas or creation output; cannot be guessed |
attributes_operations with op=delete | Removing a schema attribute affects extraction — show the current schema state first |
| Use defaults unless user specifies | Default |
|---|---|
memory_library_id | Default library |
project_id | Default project |
top_k | 10 |
page_num / page_size | 1 / 10 |
Never invent IDs. If an ID is unknown, list/query first or ask the user.
Upon loading this skill, generate a random session ID (32-char lowercase hex string) once for the entire session. Use it as {session-id} below.
Rule: Every DashScope REST API call MUST include the User-Agent header:
User-Agent: AlibabaCloud-Agent-Skills/alibabacloud-bailian-memory/{session-id}
Scripts read SKILL_SESSION_ID from the environment (default empty string) and include it in the User-Agent header automatically:
SKILL_SESSION_ID={session-id} python3 scripts/add_memory_content.py --user-id "user_123" --content "..."
Fields passed between capabilities (do not mix them up — most are look-alike IDs):
| Field | Type | Produced by | Consumed by |
|---|---|---|---|
memory_node_id | string | search / list (after async extraction completes) | get node, update, delete |
event_id | string | add_memory_messages / add_memory_content | get_event |
attribute_id | string | get_user_profile (attributes[].id) | update_user_profile (profile value ops), manage_profile_schema update |
item_id | integer | get_user_profile --need-detail (value_items[].item_id) | update_user_profile (op_type update/delete) |
profile_schema_id | string | manage_profile_schema create/list | add_memory_messages (--profile-schema), get_user_profile, update_user_profile, manage_profile_schema update |
project_id | string | manage_memory_project create/list | add_memory_messages, add_memory_content, search, list, manage_memory_project get/update |
memory_library_id | string | Bailian console (not this skill) | all scripts (optional) |
user_id | string | caller/business system | all memory & profile scripts (profile value ops take it as --entity-id) |
add_memory_messages → event_id (fire-and-forget; get_event only on demand) → list_memories/search_memory to see resultsadd_memory_content → event_id (same fire-and-forget semantics)manage_profile_schema create → profile_schema_id → add_memory_messages(--profile-schema) → get_user_profile → (correction needed) get_user_profile --need-detail for attribute_id/item_id → update_user_profile opmanage_memory_project create → project_id → add_memory_messages(--project-ids) / search_memory(--project-id)project_id ≠ memory_library_id: project is second-level isolation inside a library. Passing one as the other returns NotFound.add_memory_messages: --project-id and --project-ids are mutually exclusive.add_memory_content: binds to exactly one project — no --project-ids; no --profile-schema (profile extraction depends on conversation messages, which this mode does not take).update_user_profile operates on ONE value item per call: --op-type add appends --value; --op-type update/delete require --item-id from get_user_profile --need-detail. Invalid op_type values are rejected.manage_profile_schema update: immutable of an existing attribute cannot be changed; op=add with immutable=true requires default_value.All scripts are in scripts/ and print raw API JSON to stdout (errors go to stderr with non-zero exit). Prefix every command with SKILL_SESSION_ID={session-id}.
| User intent | Workflow |
|---|---|
| "Remember what we discussed" / persist facts from a conversation | 1. add_memory_messages (LLM extraction) |
| "Save this exact text as a memory" / import a known note | 2. add_memory_content (verbatim, no extraction) |
| Answer a question that depends on user preferences/history | 4. search_memory FIRST, then answer citing results |
| Browse/audit what is stored for a user | 5. list_memories (→ 6. get node for one detail) |
| "That memory is wrong/outdated" | 6. get node → 7. update |
| "Forget this" / remove a memory | 8. 🔴 delete flow |
| "What do we know about this user?" (structured attributes) | 9. get profile |
| "His city/job in the profile is wrong" | 9. get profile --need-detail → 10. profile value op |
| Set up / adjust memory isolation ("create a project", "switch project to lite") | 11. manage projects |
| Define / adjust which user attributes to extract ("create a profile schema", "add an attribute") | 12. manage schemas |
Use to extract memories from conversation messages via LLM. Accepted immediately (fire-and-forget); extraction runs in the background.
python3 scripts/add_memory_messages.py --user-id "<user_id>" \
--messages '[{"role":"user","content":"I prefer Python"},{"role":"assistant","content":"Noted!"}]'
# Multi-project extraction
python3 scripts/add_memory_messages.py --user-id "<user_id>" --messages '<json>' --project-ids "p1,p2"
# Also extract user profile attributes (schema from workflow 12 or the console)
python3 scripts/add_memory_messages.py --user-id "<user_id>" --messages '<json>' --profile-schema "<schema_id>"
Output: request_id, event_id, events[] (initial status PENDING). messages supports roles user/assistant/tool and standard OpenAI tool_calls/tool_call_id.
Use to store a known text directly without LLM extraction. Binds to exactly one project; profile extraction is not available in this mode (it requires conversation messages).
python3 scripts/add_memory_content.py --user-id "<user_id>" --content "<text>"
Output: request_id, event_id, events[] with resource_type prefixed custom_ (custom_observation).
Async writes are fire-and-forget — do NOT poll by default. Query only when (a) the user explicitly asks to confirm the write, or (b) a later step depends on extraction completion (e.g. immediately searching the just-written content).
python3 scripts/get_event.py --event-id "<event_id>"
Output: one record per resource; status is PENDING/SUCCEEDED/FAILED. On FAILED, read the record's detail field (errorCode: errorMessage) and report it to the user — do not silently retry. After SUCCEEDED each record carries a result[] array describing the applied changes (content, event ADD/UPDATE/DELETE, memory_node_id for observation memories, old_content for updates); for full fragment details use list/search.
python3 scripts/search_memory.py --user-id "<user_id>" --query "<search_text>"
python3 scripts/search_memory.py --user-id "<user_id>" --query "<text>" --top-k 5
Output: request_id, plan_version, memory_nodes[] (memory_node_id, content, memory_type, score, status, timestamps). Results are already relevance-filtered server-side (plan_version=pro, min_score=0.3 — tuning and billing parameters are server-controlled and not adjustable here). If no results, report that plainly; do not retry with rephrased queries more than once.
python3 scripts/list_memories.py --user-id "<user_id>" --page-num 1 --page-size 10
Output: memory_nodes[], total, page_size, page_num.
python3 scripts/get_memory_node.py --memory-node-id "<memory_node_id>"
Output: memory_node with full detail (content, memory_type, status, meta_data, ...). Always use this to show the target before update/delete confirmation.
Overwrites the fragment content. Show old (via get) and new content before executing.
python3 scripts/manage_memory.py update --memory-node-id "<id>" --user-id "<user_id>" --content "<new_content>"
Output: request_id. --meta-data merges incrementally (unspecified keys remain unchanged).
Follow the mandatory delete flow in Security Tiers — get, confirm, delete, verify.
python3 scripts/get_memory_node.py --memory-node-id "<id>" # 1. fetch & quote target content
# 2. STOP — quote content + memory_node_id, warn that deletion is irreversible,
# and wait for the user's explicit confirmation (a prior "delete X" instruction is NOT a confirmation)
python3 scripts/manage_memory.py delete --memory-node-id "<id>" # 3. run ONLY after user confirms
python3 scripts/list_memories.py --user-id "<user_id>" # 4. verify
python3 scripts/get_user_profile.py --user-id "<user_id>" --profile-schema-id "<schema_id>"
# Expand value_items (each with item_id, consumed by workflow 10 update/delete)
python3 scripts/get_user_profile.py --user-id "<user_id>" --profile-schema-id "<schema_id>" --need-detail
Output: profile with schema_name, schema_description, attributes[]. Default mode joins values into one string per attribute (id, name, value; value is null until extracted); --need-detail returns value_items[] (item_id, value, status) per attribute instead. Profile data accumulates over multiple conversations — an empty value is normal early on, not an error. The id field is the attribute_id used by workflow 10.
Operates on ONE value item per call. Use when the user explicitly states new facts
that should correct the profile. Show current values (via get --need-detail) and
the intended change before executing.
# Add a new value item (no item_id needed)
python3 scripts/update_user_profile.py --entity-id "<user_id>" --profile-schema-id "<schema_id>" \
--attribute-id "<attr_id>" --op-type add --value "swimming"
# Update an existing value item (item_id from get --need-detail)
python3 scripts/update_user_profile.py --entity-id "<user_id>" --profile-schema-id "<schema_id>" \
--attribute-id "<attr_id>" --op-type update --item-id 5634 --value "volleyball"
# Delete an existing value item
python3 scripts/update_user_profile.py --entity-id "<user_id>" --profile-schema-id "<schema_id>" \
--attribute-id "<attr_id>" --op-type delete --item-id 5634
Output: request_id. Other value items remain unchanged; verify via get_user_profile.py --need-detail.
# Create
python3 scripts/manage_memory_project.py create --name "observation-project" \
--plan-version pro --expired-in-days 30
# List / detail
python3 scripts/manage_memory_project.py list
python3 scripts/manage_memory_project.py get --project-id "<project_id>"
# Update (at least one updatable field required)
python3 scripts/manage_memory_project.py update --project-id "<project_id>" --plan-version lite
Output: create returns project_id; list/get return project detail incl. plan_version. Before update, run get and show current settings — PATCH overwrites each provided field, so the user must see the values being replaced to give informed confirmation.
# Create (an attribute with immutable=true requires default_value)
python3 scripts/manage_profile_schema.py create --name "basic-user-profile" \
--attributes '[{"name":"Name","immutable":true,"default_value":"Zhang San"},{"name":"Hobbies"}]'
# List
python3 scripts/manage_profile_schema.py list
# Update (attribute changes via attributes-operations; op=update/delete require attribute_id)
python3 scripts/manage_profile_schema.py update --profile-schema-id "<schema_id>" \
--attributes-operations '[{"op":"add","name":"Favorite Music"},{"op":"delete","attribute_id":"<attr_id>"}]'
Output: create returns profile_schema_id; list returns profile_schemas[] (profile_schema_id, name, description, plan_version). attribute_id for update/delete ops comes from get_user_profile output (attributes[].id). Before an update with op=delete, show the current schema attributes.
content in natural language ordered by score; do not expose memory_node_id or other internal IDs.memory_node_id alongside content, since the user needs the ID to act.User: "Customer customer_zhang_001 is reaching out again. Check what preferences I should keep in mind, and after the chat save the key points of this conversation."
Flow:
search_memory.py --user-id customer_zhang_001 --query "communication preferences service restrictions" → 2 nodes: "Prefers communication in Chinese" (score 0.71), "Refuses marketing pushes" (score 0.65)add_memory_messages.py --user-id customer_zhang_001 --messages '<conversation JSON of this round>' → returns event_id, fire-and-forget, no pollingget_event.py and report per-resource status.| Error | Cause → Action |
|---|---|
401 InvalidApiKey | Key invalid/unconfigured, or a key whose environment does not match the endpoint (e.g. a pre-release key against the production gateway) → guide user through Initial Setup step 1 |
403 Forbidden | Memory service not activated → guide user through Initial Setup step 2; if it occurs during API key auto-creation, check RAM permissions per references/ram-policies.md |
404 NotFound | Wrong memory_node_id/event_id/profile_schema_id, or project_id passed as memory_library_id → re-check via list/get |
For other codes see references/error-handling.md. Do not guess error meanings; report the API message as returned.
After completing workflows, verify using commands in references/verification-method.md.
Quick checklist:
event_id returned; extracted fragments visible via list/search afterwardsevent_id returned with custom_* resource_typescore/memory_type/plan_version--need-detail (added/updated/deleted item)project_id; update reflected in a subsequent getprofile_schema_id; attribute operations reflected in extraction behavior# List to get IDs, then delete each (follow the 🔴 delete flow)
SKILL_SESSION_ID={session-id} python3 scripts/list_memories.py --user-id "<user_id>"
SKILL_SESSION_ID={session-id} python3 scripts/manage_memory.py delete --memory-node-id "<memory_id>"
user_id values — descriptive identifiers (e.g., customer_12345) make memory ownership auditable.event_id rather than the extracted fragments. Choose by intent: extract from conversation → add_memory_messages.py; store known text as-is → add_memory_content.py. To confirm a write, check the event or verify via list/search.top_k 3-10 beats broad terms; the server already filters low-relevance results.plan_version/instruction_type when the user explicitly requires them; server defaults (pro/default) fit most cases.scripts/api_key.py; never hardcode, export, extract, or expose key values.| Reference | Contents |
|---|---|
references/api-endpoints.md | Detailed documentation for the 16 REST API endpoints used by this skill |
references/error-handling.md | Error codes, retry strategies, and troubleshooting guide |
references/ram-policies.md | RAM permissions required for API Key auto-create/delete via CLI |
references/verification-method.md | Success verification steps per workflow |
references/acceptance-criteria.md | Test acceptance criteria and correct patterns |
references/related-commands.md | Complete command reference for all scripts |
| Bailian Console | Obtain DASHSCOPE_API_KEY; delete profile schemas & memory projects |
| Memory Library Docs | Official product documentation |
| Memory API Reference | Official API reference |