Files
Hy-Memory/hy_memory/prompts/en.py
T
alvinfeiandClaude Opus 5.5 16da11f788 feat(sdk): prompts in 11 languages and an explicit language option
Move every LLM prompt that had a zh variant into one registry,
hy_memory/prompts/, with one module per language (en, zh-Hans, zh-Hant,
ja, es, de, pt, fr, ko, ru, id). Call sites look prompts up with
get_prompt(name, language), so a new language needs only a new module.
The en and zh-Hans strings are moved verbatim, a test pins their sha256,
and the old names (prompts_zh.*_ZH, extractor.EXTRACT_SYSTEM_PROMPT, ...)
still import.

Add an optional language: MemoryConfig.language (env MEMORY_LANGUAGE)
as the client default, plus a per-call language= on add / async_add,
add_extracted, finalize_session, evolve and sweep. It goes through
extract and basic profile, reconcile and search query, the coding
extractor and both coding reconcilers with the chain summary, the
episode and topic summaries, and System 2. Codes are validated, with
aliases (zh, zh-CN, zh-TW, zh-HK, pt-BR); an unknown code raises
ValueError. With an explicit language, memories are written in that
language. Without one, the zh/en auto-detection is unchanged. The
server accepts language on /add and /add_extracted.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-11-01 21:29:59 +08:00

2306 lines
113 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""English prompts (``en``).
The source of truth for every other language. Moved verbatim from the
modules that use them; those modules re-export the old names.
"""
# ================================================================
# Chat extraction (agent/extractor.py)
# ================================================================
EXTRACT_SYSTEM_PROMPT = """# ROLE
You are a Memory Extractor.
Extract durable, high-value memories from conversations and convert them into structured, self-contained statements.
Only extract information that is likely to matter in future conversations.
---
# INPUTS
You will receive:
* Last k Messages
* New Messages
* Memory Date
* Current Date
Use:
* Last k Messages only for coreference resolution
* Memory Date as the ONLY temporal anchor
* Current Date only as metadata, never for resolving relative time
---
# WHAT TO EXTRACT
Extract ONLY genuinely new information introduced in the NEW user messages.
Good memories include:
* preferences
* long-term projects
* habits
* goals
* recurring activities
* opinions
* relationships
* important experiences
* stable traits
* future plans
* decisions
* constraints
* accepted recommendations
Do NOT extract:
* small talk
* acknowledgements
* generic emotions
* temporary conversational context
* assistant reminders of old memories
* information already implied by prior context
* vague personality judgments
---
# SUBJECT
Every memory must be self-contained, written in third person, with an explicit
subject. Which subject to use is decided by **who spoke**:
| Speaker | Subject to write | Example |
| --- | --- | --- |
| `role: "user"` | the user | "The user ..." / "用户..." |
| any speaker with a `name` other than the user (e.g. `name: "John"`) | that person, by name | "John ..." |
| `role: "assistant"` with NO `name` | the AI assistant | "The assistant ..." |
The two input sections express the speaker differently:
* **New Messages** is a JSON array — read `role` and the optional `name` field.
* **Last k Messages** is rendered text — the speaker is the bracket label,
`[user]:` / `[John]:` / `[assistant]:`, optionally preceded by one line
"The user's name is Tim" / "用户名叫Tim".
Rules:
* The user is the person these memories belong to. Even when the user's own name
is known (from `name` on their message, or from the leading name line), still
write "The user ...", NEVER "Tim ...".
* **`role: "assistant"` WITH a `name` is another real person, not the AI.**
Write their memories with their name as the subject: "John ...".
* Only a nameless `role: "assistant"` is the AI assistant.
* Never invent a name for the AI assistant, it has none.
---
# LANGUAGE RULES
* Output language must match input language.
* Preserve original scripts and technical terms exactly.
* Never transliterate names, brands, or equations.
---
# TWO KINDS OF MEMORY
You extract two kinds of memory: **memory** and **intentions**.
## memory
Everything durable and worth remembering about the user — and about any other
named person in the conversation — goes here:
* preferences, opinions, values, attitudes
* events, actions, experiences
* habits, behavioral patterns
* relationships, traits
* objective facts, decisions, constraints
* ongoing activities
Implicit preferences MUST be inferred when strongly suggested
(e.g. resuming an activity → positive interest; avoiding something →
negative preference; repeated voluntary effort → sustained interest).
Do NOT separate "what kind of person the user is" from "what happened".
Both go into `memory`.
## intentions
A forward-looking thing the user **plans or intends to do in the future**,
which can be acted on and which becomes irrelevant once done or past.
Examples:
* "The user has a job interview next Tuesday."
* "The user plans to move apartments at the end of the month."
* "The user intends to finish the report before Friday."
NOT intentions (these belong in `memory`):
* stable preferences / traits ("The user likes basketball")
* completed past events ("The user moved to Beijing last year")
* general goals with no actionable future step
### valid_until
For each intention, provide `valid_until`: the date after which the
intention no longer matters (the deadline / target date), resolved
against **Memory Date** (e.g. "next Tuesday" → that absolute date).
If no deadline can be determined, set `valid_until` to `null`.
---
# OWNER
Every memory and intention MUST carry an `owner` saying which memory store it
belongs to. There are exactly two values:
* `"user"` — anything belonging to the user's own memory store. This includes
facts/preferences/traits about the user themselves, AND facts about other
named real people in the conversation, because those are still things the
user's memory should hold.
* `"agent"` — information the AI assistant (a nameless `role: "assistant"`)
provides (recommendations, confirmations, things it researched), or a future
action the user wants the **assistant** to perform ("remind me in 8 hours").
A named person is NEVER `owner: "agent"` — they are a real person, not the AI,
even though their messages carry `role: "assistant"`. Their memories are
`owner: "user"` with their name as the content subject.
Decide `owner` per item. When unsure, default to `"user"`.
---
# TEMPORAL RULES
Resolve relative time using Memory Date.
Examples:
* "next week"
* "last month"
* "tomorrow"
If time cannot be resolved safely:
* remove the relative wording
* keep the memory atemporal
Do not invent chronology.
**Just-happened events are events — keep them.** When the user reports something as
just completed ("just got back", "just finished", "this morning", "yesterday"), the
completion date is the Memory Date. Emit the **event itself** with that date; do not
fold it into a background statement.
* "I just got back from a trip with my family"
→ "The user returned from a family trip on <Memory Date>."
NOT → "The user takes family trips." (the return date is the answer to "when did
they get back", and dropping it makes the question unanswerable)
Where an event has both a start and an end that the conversation gives separately,
record both rather than collapsing them into one span.
---
# QUALITY RULES
Each memory must be:
* self-contained
* contextually complete
* retrieval-friendly
* information-dense
Retain important details:
* names
* products
* locations
* numbers
* equations
* constraints
Never generalise a specific into a category. Write what was actually said.
* "Banff" → keep "Banff", NOT "the Rocky Mountains"
* "a 3-year-old border collie named Luna" → keep the breed, age and name
**Specifics supplied by the other speaker still count.** A fact is often built across
turns: the other speaker names the thing, the user confirms it. The subject rules
decide *whose* memory it is — they do not license dropping detail.
* [Sam]: "Did you take advantage of the skiing opportunities in Banff?"
[user]: "Yeah, it was great for skiing!"
→ "The user went skiing in Banff and found the snow amazing."
NOT → "The user enjoys winter sports." (both the place and the activity were
established, one by each speaker)
Avoid fragmentary memories lacking context.
Choose the smallest number of memories that still preserves distinct future
retrieval needs.
* Compression means combining, not omitting. Every explicit durable proposition
in the new messages must appear exactly once in the output.
* Combine details when they share the same subject and the same real-world event,
state, preference, plan, or decision. A coherent event may use 1–3 sentences.
* Do not emit both a broad statement and narrower restatements of the same evidence.
* Split only when the subject differs, the events happened independently, or the
memories describe different states.
* Keep a cause, recommendation, or consequence with the fact it explains when
separating them would lose the relationship.
* Never merge separate dated occurrences merely because they share a topic.
* Preserve the source's time precision. Do not turn vague phrases such as "last
weekend" or "last summer" into exact calendar dates unless the date is explicit
or uniquely determined; "the weekend before <Memory Date>" is acceptable.
---
# TAGS
Each memory must include 1–3 concise tags.
- Topical keywords stay lowercase (travel, health, work).
- Proper nouns keep capitalization (Jasper, Canada, Banff, Prius).
Examples:
* work
* travel
* Jasper
* Canada
* health
---
"""
STRUCTURE_METADATA_PROMPT = """
---
# STRUCTURAL ORGANIZATION
For every `memory`, also output:
* `topic_paths`: a backward-compatible list of zero to three broad-to-specific
semantic paths.
* `event_facet`: an object with only `subject`, `participants`, `action`,
`objects`, `locations`, and `state`; state is `planned`, `ongoing`,
`completed`, or `unknown`.
* `topic_assignments`: zero to six subject-specific memberships. Each item is
`{subject, role, path}`, where `path` is a list of `{name, type}` segments and
`type` is one of `topic`, `facet`, `series`, or `episode`. Use `topic` for a
broad domain, `facet` for a concrete activity/semantic aspect, `series` only
for repeated occurrences of the same concrete activity/state line, and
`episode` only for one bounded real-world occurrence. A shared event may have
one assignment per explicit participant while keeping one canonical memory.
Types follow `topic -> facet -> series -> episode`; levels may be skipped but
types may not repeat or move backward.
This extractor sees only the current write and therefore must not emit a
`series` segment. Emit Topic/Facet/Episode here. Reconcile may later establish a
Series after comparing independent stored occurrences.
This is write-time organization. Do not infer Series/Episode from path depth,
manufacture hierarchy from a proper noun or broad tag, or assign a person only
because they co-occur in unrelated context. Stop at the most specific supported
level and use empty values when uncertain. Never encode query forms such as
"when", "how many", "who", plurality, or ordinal wording.
"""
EXTRACT_FEW_SHOT = """
---
# EXAMPLES
## Example 1 — Boundary: old context is for resolution only, never re-extracted
Last k Messages:
[{"role": "user", "content": "I've been looking at rescue dogs at the shelter downtown."},
{"role": "assistant", "content": "That's lovely — found one you like?"}]
New Messages:
[{"role": "user", "content": "Yeah, picked her up yesterday — a 3-year-old border collie named Luna."}]
Memory Date: 2025-04-10
Output:
```json
{
"memory": [
{"content": "The user adopted a 3-year-old border collie named Luna from a downtown shelter on 2025-04-09.", "tags": ["pets", "family"], "owner": "user"}
],
"intentions": [],
"basic_info": {}
}
```
The old messages only resolve "her" and supply context ("downtown shelter"). Do NOT emit a stale memory like "the user is considering adopting a dog" — only the genuinely new fact (the completed adoption) is extracted, with "yesterday" resolved against Memory Date.
## Example 2 — Condense a long assistant reply
New Messages:
[{"role": "user", "content": "Write me a short essay on why remote work boosts productivity."},
{"role": "assistant", "content": "Sure — opening: 'Remote work has reshaped how teams operate...' [~2000 words of body omitted] ...closing: 'In short, autonomy and fewer interruptions drive higher output.'"}]
Memory Date: 2025-03-22
Output:
```json
{
"memory": [
{"content": "The assistant drafted a short essay for the user arguing that remote work boosts productivity because autonomy and fewer interruptions drive higher output.", "tags": ["writing", "remote-work"], "owner": "agent"}
],
"intentions": [],
"basic_info": {}
}
```
The assistant's reply is long. Never store the verbatim essay — distill it to topic + thesis. owner is "agent" because the assistant produced it. Bloated verbatim content makes memory unusable.
## Example 3 — Composite extraction: basic_info + intention + condensed agent plan
New Messages:
[{"role": "user", "content": "Hi, I'm Kenji. I'm planning a trip to Japan next month — could you put together a 7-day itinerary?"},
{"role": "assistant", "content": "# 7-Day Classic Japan Golden Route (Tokyo → Kyoto → Nara → Osaka)\\nFly into Tokyo, Shinkansen south to Kyoto, day-trip Nara, depart from Osaka (Kansai).\\n\\n## Day 1: Arrival in Tokyo\\n- Land at Haneda/Narita, drop bags at hotel near Tokyo Station.\\n- Late afternoon: Sensoji Temple (Asakusa), wander Nakamise Street, try taiyaki.\\n- Evening: Shibuya Crossing + Shibuya Sky rooftop for sunset; izakaya dinner (yakitori, sake).\\n\\n## Day 2: Tokyo markets & landmarks — [omitted]\\n## Day 3: Shinkansen to Kyoto, Fushimi Inari, Gion — [omitted]\\n## Day 4: Kyoto temples (Kiyomizu-dera, Kinkaku-ji, Ginkaku-ji) — [omitted]\\n## Day 5: Nara day trip (Nara Park deer, Todai-ji) — [omitted]\\n## Day 6: Osaka (Osaka Castle, Dotonbori street food) — [omitted]\\n## Day 7: Osaka sightseeing + depart from Kansai Airport — [omitted]\\n\\nTip: buy a 7-day JR Pass before arriving. Want me to adjust anything?"}]
Memory Date: 2025-09-15
Output:
```json
{
"memory": [
{"content": "The assistant created a 7-day Japan itinerary for the user along the classic golden route: Tokyo (Asakusa, Shibuya), Shinkansen to Kyoto (Fushimi Inari, Kiyomizu-dera, Kinkaku-ji, Gion), a Nara day trip (deer park, Todai-ji), then Osaka (Osaka Castle, Dotonbori), departing from Kansai Airport.", "tags": ["travel", "japan", "itinerary"], "owner": "agent"}
],
"intentions": [
{"content": "The user plans to take a 7-day trip to Japan in October 2025.", "tags": ["travel", "japan"], "valid_until": "2025-10-31", "owner": "user"}
],
"basic_info": {"name": "Kenji"}
}
```
One turn produces three outputs in different buckets: the name goes to basic_info, the trip is a forward-looking intention with a deadline, and the assistant's long multi-section itinerary is condensed into ONE dense agent memory — the route and key stops per city, NOT every day's bullets, tips, or food lists. Storing the whole reply verbatim would bloat memory; storing the skeleton keeps it queryable.
## Example 4 — A later message confirming/adopting an earlier recommendation is itself a fact
New Messages:
[{"role": "user", "content": "My 6-year-old won't eat any vegetables. Any ideas?"},
{"role": "assistant", "content": "Try blending spinach or carrots into pasta sauce — the color and flavor mostly disappear, and many kids accept it without noticing."},
{"role": "user", "content": "Oh nice, we'll try that tonight. By the way, what's a good bedtime for that age?"},
{"role": "assistant", "content": "For a 6-year-old, aim for 7–9 PM with about 10–11 hours of sleep."}]
Memory Date: 2025-06-20
Output:
```json
{
"memory": [
{"content": "The user has a 6-year-old child who refuses to eat vegetables, and accepted the assistant's suggestion to blend spinach or carrots into pasta sauce to hide them.", "tags": ["parenting", "food"], "owner": "user"},
{"content": "The assistant advised the user that a 6-year-old should aim for a 7–9 PM bedtime with about 10–11 hours of sleep.", "tags": ["parenting", "sleep"], "owner": "agent"}
],
"intentions": [],
"basic_info": {}
}
```
In the third message the user said "we'll try that tonight" — explicitly adopting the earlier suggestion. That acceptance is valuable and must be recorded ("accepted the assistant's suggestion to ..."), not discarded as small talk. The bedtime advice was just given and not yet adopted, so it is recorded as advice (owner "agent").
## Example 5 — RAG/injected context: never store the injected text
New Messages:
[{"role": "user", "content": "[Retrieved context: The Eiffel Tower is 330m tall and was completed in 1889.] Given the above, I'm going to propose to my girlfriend at the top of the Eiffel Tower this summer."}]
Memory Date: 2025-05-02
Output:
```json
{
"memory": [],
"intentions": [
{"content": "The user plans to propose to his girlfriend at the top of the Eiffel Tower in summer 2025.", "tags": ["relationship", "travel"], "valid_until": "2025-08-31", "owner": "user"}
],
"basic_info": {}
}
```
The bracketed retrieved context (the tower's height and year) is RAG-injected reference material, NOT something the user told us about themselves — do NOT store it. Only the user's own forward-looking plan is extracted. System/RAG-injected text is context for the model, never a memory.
## Example 6 — Shared reference material: extract the content, not the sharing action
New Messages:
[{"role": "user", "content": "Remember my grandma's dumpling recipe so I don't lose it. Filling: 500g ground pork, 300g napa cabbage, 2 tbsp soy sauce, 1 tbsp sesame oil, a thumb of ginger minced. Cabbage salted 15 min then squeezed dry. Boil dumplings, add cold water twice, ready when they float."},
{"role": "assistant", "content": "Got it, saved!"}]
Memory Date: 2025-07-01
Output:
```json
{
"memory": [
{"content": "The user's grandmother's dumpling recipe: filling of 500g ground pork, 300g napa cabbage (salted 15 minutes then squeezed dry), 2 tbsp soy sauce, 1 tbsp sesame oil, and a thumb of minced ginger; boil the dumplings, add cold water twice, and they are done when they float.", "tags": ["food", "recipe", "family"], "owner": "user"}
],
"intentions": [],
"basic_info": {}
}
```
Extract the actual factual content (ingredients, quantities, the salt-and-squeeze step, the boiling method) — NOT "the user shared a recipe" or "the user asked to remember the recipe". The sharing action is worthless; the content is what's queryable.
## Example 7 — Structured numbers and attributes preserved exactly
New Messages:
[{"role": "user", "content": "Logging my home-gym PRs this cycle: Squat 140kg x 3, Bench 100kg x 5, Deadlift 180kg x 2, Overhead Press 60kg x 6. Bodyweight 78kg, 3 sets per lift, 3 min rest."},
{"role": "assistant", "content": "Logged — strong numbers!"}]
Memory Date: 2026-01-20
Output:
```json
{
"memory": [
{"content": "The user's current lifting PRs are Squat 140kg x 3, Bench 100kg x 5, Deadlift 180kg x 2, and Overhead Press 60kg x 6, at a bodyweight of 78kg.", "tags": ["fitness", "strength"], "owner": "user"},
{"content": "The user trains with 3 sets per lift and 3 minutes of rest between sets.", "tags": ["fitness", "training"], "owner": "user"}
],
"intentions": [],
"basic_info": {}
}
```
Every number and unit (kg, reps, sets, rest time) is preserved exactly — dropping them would destroy the most queryable information. Split into two clean topics (PRs vs. training protocol), but never round or omit the specifics.
## Example 8 — Long multi-topic conversation: do not stop after the first topic
New Messages:
[{"role": "user", "content": "Big week — I finally defended my PhD thesis on Tuesday!"},
{"role": "assistant", "content": "Huge congrats! How do you feel?"},
{"role": "user", "content": "Relieved! I also signed a lease on an apartment in Berlin and move next month. And I've started learning German on Duolingo — 30-day streak. My partner Sofia is staying in Madrid for now, which is hard."}]
Memory Date: 2025-11-08
Output:
```json
{
"memory": [
{"content": "The user defended their PhD thesis on 2025-11-04.", "tags": ["education", "milestone"], "owner": "user"},
{"content": "The user signed a lease on an apartment in Berlin.", "tags": ["housing"], "owner": "user"},
{"content": "The user has been learning German on Duolingo and has a 30-day streak.", "tags": ["language", "learning"], "owner": "user"},
{"content": "The user's partner Sofia is staying in Madrid for now, which the user finds hard.", "tags": ["relationship"], "owner": "user"}
],
"intentions": [
{"content": "The user plans to move to Berlin next month (around December 2025).", "tags": ["housing", "relocation"], "valid_until": "2025-12-31", "owner": "user"}
],
"basic_info": {}
}
```
Four+ topics — thesis defense, the Berlin apartment, learning German, and the partner in Madrid. Do not stop after the first topic. Note one turn split into a completed fact (lease signed) AND an intention (the upcoming move), and the emotional-but-substantive detail about Sofia is kept because it carries lasting relationship context.
## Example 9 — Two real people: `name` on an assistant message means a real person
New Messages:
[{"role": "user", "name": "Tim", "content": "Just got back from the climbing gym, third time this week. Finally sent that overhang route."},
{"role": "assistant", "name": "John", "content": "Nice! I've been swimming instead — my shoulder still hurts from last month."}]
Memory Date: 2025-08-14
Output:
```json
{
"memory": [
{"content": "The user has been climbing three times a week and recently completed a difficult overhang route.", "tags": ["fitness", "climbing"], "owner": "user"},
{"content": "John has been swimming instead of other exercise because his shoulder still hurt after an injury around July 2025.", "tags": ["fitness", "health"], "owner": "user"}
],
"intentions": [],
"basic_info": {"name": "Tim"}
}
```
John carries `role: "assistant"` but he has a `name`, so he is another real person, NOT the AI: his memory uses "John ..." as the subject and is still `owner: "user"` (it belongs to the user's memory store). The user's own memory stays "The user ..." even though we know the name is Tim — never "Tim has been climbing". `basic_info.name` comes from `name: "Tim"` on the user message, which is authoritative even though Tim never said his own name out loud.
## Example 10 — Counter-example: another person's attributes never go into basic_info
New Messages:
[{"role": "user", "name": "Tim", "content": "你最近在忙什么?"},
{"role": "assistant", "name": "John", "content": "我在柏林一家医院当护士,上的是夜班,挺累的。"}]
Memory Date: 2025-09-02
Output:
```json
{
"memory": [
{"content": "John是柏林一家医院的护士,上夜班,觉得很累。", "tags": ["work", "health"], "owner": "user"}
],
"intentions": [],
"basic_info": {"name": "Tim"}
}
```
John's occupation and location are HIS attributes, so they go into `memory` with "John" as the subject. They must NEVER land in `basic_info.occupation` / `basic_info.location` — `basic_info` is exclusively the user's own profile. Only `name` (Tim, from the user message's `name` field) belongs there.
## Example 11 — Counter-example: small talk has no basic_info
New Messages:
[{"role": "user", "content": "Hi"},
{"role": "assistant", "content": "Hello! The user's name is The user. How can I help?"}]
Memory Date: 2025-09-03
Output:
```json
{
"memory": [],
"intentions": [],
"basic_info": {}
}
```
The user stated nothing about themselves. The assistant's line is not a user turn, and "The user" is a placeholder, not a name. Output `{}` — never `{"name": "The user"}` or `{"name": "user"}`.
---
"""
EXTRACT_PROMPT = """## Last k Messages
{last_messages}
---
## New Messages
{content}
---
## Memory Date
{memory_at}
---
## Current Date
{current_date}
---
# Output Format
Return ONLY a JSON object inside a ```json fenced block.
```json
{{
"memory": [
{{
"content": "...",
"tags": ["..."],
"owner": "user"
}}
],
"intentions": [
{{
"content": "...",
"tags": ["..."],
"valid_until": "YYYY-MM-DD",
"owner": "user"
}}
],
"basic_info": {{}}
}}
```
Rules:
* `memory` = durable info worth remembering: preferences, opinions, attitudes,
traits, events, plans, experiences, habits, decisions — about the user or
about any other named real person, with the subject written per "# SUBJECT"
* `intentions` = forward-looking things the user (or another named person) plans /
intends to do in the future, with `valid_until` = the deadline/target date
(resolved against Memory Date) or `null` if none
* `owner` (required on every memory & intention) = `"user"` or `"agent"`
(see "# OWNER" above; default `"user"` when unsure)
* `basic_info` = the **user's own profile only**. Include stable profile
fields (e.g. name, age, location, occupation, employer) **explicitly stated**
in the NEW user messages. Never infer, never invent. If no field is explicitly
stated, output `{{}}`.
* HARD RULE: fill a field ONLY if the user stated it in a `user` turn.
Anything said only by the assistant (including the assistant quoting an
earlier memory such as "The user's name is X") does NOT count. If nothing
qualifies, output `"basic_info": {{}}`.
* NEVER write the field name itself or a placeholder as a value ("user",
"The user", "用户", "name", "N/A", "unknown", "none", "-"). Omit the field.
* Negative example: small talk such as `[user]: Hi` / `[assistant]: How can
I help?`, or `[user]: Reply with exactly: PONG` → `"basic_info": {{}}`.
* Exception for `name`: the `name` field on the user's own message, or a leading
line "The user's name is X" / "用户名叫X", is an authoritative source for
`name` — even though the user never said it out loud.
* NEVER put another real person's attributes here. If John (an assistant-role
message carrying `name`) says "I'm a nurse in Berlin", that is a `memory`
("John is a nurse in Berlin."), NOT `basic_info.occupation` / `.location`.
* No duplicate information across sections
* Tags must contain 1–3 concise lowercase keywords
* Use empty arrays `[]` or `{{}}` when nothing is extracted
* Return valid JSON only
Output the JSON now."""
EXTRACT_PROMPT_NO_INTENTION = """## Last k Messages
{last_messages}
---
## New Messages
{content}
---
## Memory Date
{memory_at}
---
## Current Date
{current_date}
---
# Output Format
Return ONLY a JSON object inside a ```json fenced block.
```json
{{
"memory": [
{{
"content": "...",
"tags": ["..."],
"owner": "user"
}}
],
"basic_info": {{}}
}}
```
Rules:
* `memory` = durable info worth remembering: preferences, opinions, attitudes,
traits, events, experiences, habits, decisions, **and future plans / upcoming
actions** — about the user or about any other named real person, with the
subject written per "# SUBJECT". Do **not** invent a separate intentions bucket.
* `owner` (required on every memory) = `"user"` or `"agent"`
(see "# OWNER" above; default `"user"` when unsure)
* `basic_info` = the **user's own profile only**. Include stable profile
fields (e.g. name, age, location, occupation, employer) **explicitly stated**
in the NEW user messages. Never infer, never invent. If no field is explicitly
stated, output `{{}}`.
* HARD RULE: fill a field ONLY if the user stated it in a `user` turn.
Anything said only by the assistant (including the assistant quoting an
earlier memory such as "The user's name is X") does NOT count. If nothing
qualifies, output `"basic_info": {{}}`.
* NEVER write the field name itself or a placeholder as a value ("user",
"The user", "用户", "name", "N/A", "unknown", "none", "-"). Omit the field.
* Negative example: small talk such as `[user]: Hi` / `[assistant]: How can
I help?`, or `[user]: Reply with exactly: PONG` → `"basic_info": {{}}`.
* Exception for `name`: the `name` field on the user's own message, or a leading
line "The user's name is X" / "用户名叫X", is an authoritative source for
`name` — even though the user never said it out loud.
* NEVER put another real person's attributes here. If John (an assistant-role
message carrying `name`) says "I'm a nurse in Berlin", that is a `memory`
("John is a nurse in Berlin."), NOT `basic_info.occupation` / `.location`.
* No duplicate information across sections
* Tags: 1–3 concise keywords; topical tags lowercase; proper nouns keep capitalization
* Use empty arrays `[]` or `{{}}` when nothing is extracted
* Return valid JSON only — **never** include an `intentions` key
Output the JSON now."""
# ================================================================
# Chat reconcile (agent/reconciler.py)
# ================================================================
SEARCH_QUERY_PROMPT = """You are a search query generator. Given a list of newly extracted memories, generate a set of short search queries that can be used to find related existing memories in a vector database.
The goal is to maximize recall — find existing memories that are semantically related to the new memories, even if the wording is very different.
## New memories:
{new_memories}
## Instructions
Generate search queries that cover:
- Key topics, entities, and themes mentioned in the memories
- Rephrased or abstracted versions of the core concepts
- Related concepts that might exist in the user's memory store
Output a JSON array of query strings (5-15 queries, short and focused):
["query1", "query2", "query3", ...]
Output JSON array only, no other text."""
RECONCILE_PROMPT = """You are a memory management system.
Your task is to merge new memories into an existing memory base while keeping it:
* lossless
* non-contradictory
* compact
* highly retrievable
---
# Operations
## ADD
Use when the information is entirely new.
```json
{{
"op": "ADD",
"content": "...",
"owner": "user",
"tags": ["..."]
}}
```
---
## UPDATE
Use when:
* the new memory is compatible with an existing one
* they describe the same topic
* merging improves completeness or retrievability
Rules:
* merge losslessly
* preserve all useful information
* do NOT create duplicates
* if the two disagree on a value, keep the value from the memory with the later
`memory_at`; never let an older memory overwrite a newer value
### Same occurrence vs. another occurrence
Use **UPDATE** only when the new memory adds detail to the same real-world
occurrence or durable fact already recorded.
If the same person does the same kind of activity/event again at another time
(another trip, game, injury, purchase, health scare, etc.), keep the occurrence
as a separate **ADD**. Preserve its own date and details. When an existing memory
is clearly another occurrence in that same concrete series, link it with
`related: [{{"memory_id":"...","relation":"same_series"}}]`.
Do not merge separate dated occurrences into one long count/timeline memory.
The retrieval layer builds timelines from the linked L2 memories.
```json
{{
"op": "UPDATE",
"memory_id": "...",
"content": "...",
"owner": "user",
"tags": ["..."]
}}
```
---
## SUPERSEDE
Use ONLY when:
* the new memory contradicts an existing memory
* both cannot be true at the same time
* the old memory would now give the wrong current-state answer
* the new memory's `memory_at` is the same as or LATER than the existing memory's `memory_at`
Examples:
* city changed
* employer changed
* relationship status changed
Do NOT use for:
* added detail
* compatible preferences
* wording differences
* semantic equivalence
* a new memory whose `memory_at` is EARLIER than the existing memory's — it is an
older state and must never replace a newer one (see "Time order")
Rules:
* content must contain ONLY the new state
* do NOT copy unchanged information
* the system keeps the old node and links it onto the evolution chain automatically; you don't need to handle history
```json
{{
"op": "SUPERSEDE",
"memory_id": "...",
"content": "...",
"supersede_reason": "...",
"owner": "user",
"tags": ["..."]
}}
```
### Folding several existing memories into one chain
If TWO OR MORE existing memories track the SAME dimension and the new memory is
the latest change on that dimension — its `memory_at` is later than every folded
memory's `memory_at` — you may fold them into a single evolution
chain. Use `memory_ids` (an ordered list, OLDEST → NEWEST) instead of a single
`memory_id`:
```json
{{
"op": "SUPERSEDE",
"memory_ids": ["<oldest_id>", "<newer_id>"],
"content": "<only the newest state>",
"supersede_reason": "...",
"owner": "user",
"tags": ["..."]
}}
```
The listed memories are chained oldest → newest and the new memory becomes the
chain head. `content` still describes ONLY the newest state — never copy the
older memories' text into it; they remain queryable in the chain history.
---
# Rules
* Preserve all meaningful information.
* Skip exact duplicates.
* Prefer merging related fragments.
* Separate dated occurrences stay separate ADDs; use `same_series` only for the
same subject and same concrete activity/event type.
* Keep each memory focused on one coherent topic.
* Follow "Time order" below.
* **NEVER merge or supersede across different subjects.** The subject is whoever
the content is about: "The user ..." vs "John ..." vs "Mary ...". Memories about
different people are independent facts even when the sentence is otherwise
identical — "The user likes coffee" and "John likes coffee" are two separate
memories, never an UPDATE or a SUPERSEDE of each other.
* `owner` (`user`/`agent`): copy it from the memory being written. NEVER merge or supersede across different owners — a user's fact and the assistant's fact are distinct even on the same topic.
* A SUPERSEDE either targets ONE memory (`memory_id`) or folds several memories on the same dimension into one chain (`memory_ids`, oldest→newest). An UPDATE targets exactly ONE memory.
* Never touch the same existing memory from more than one op.
* Output language matches input memory language.
---
# Time order
Every memory, new or existing, has `memory_at`: when it happened. Memories are
NOT always written in the order they happened — history can be imported late,
and a memory can be added while older history is still being processed. A new
memory can therefore be OLDER than an existing one.
* Decide which memory is newer ONLY by `memory_at`. "New memories" means "being
written now", not "more recent".
* An older memory must never replace a newer one: no SUPERSEDE of a newer
memory, and no UPDATE that overwrites a newer value with an older one.
* If a new memory is older than an existing memory it conflicts with, keep the
existing memory as the current state. ADD the new one as a past state, worded
in the past with its time (for example "In 2023 the user lived in Shanghai"),
or output nothing if it adds no information.
* `memory_at: null` means unknown time. Unknown is NOT newer: never SUPERSEDE a
memory that has a known `memory_at` with one whose `memory_at` is null.
* Do not invent chronology.
---
# Optional: `related`
On any op you MAY add a `related` array recording how the memory being written
relates to an EXISTING memory. This does not change what gets stored — it only
records a link.
```json
{{
"op": "ADD",
"content": "...",
"owner": "user",
"tags": ["..."],
"related": [{{"memory_id": "<existing_id>", "relation": "causes"}}]
}}
```
Only these four relations are allowed:
* `causes` — the two memories are in a cause/effect relationship
(e.g. "started running daily" → "knee started hurting")
* `elaborates` — adds detail to, or is a later development of, the existing memory
* `contrasts` — they are in tension but BOTH still true (if one makes the other
false, that is a SUPERSEDE, not a `contrasts` link)
* `same_series` — separate dated occurrences of the same concrete activity or
event type for the same subject (for example, two different skiing trips)
Rules:
* Only link to memories that appear in "Existing memories" — never invent an id.
* Only emit a link when the relationship is **explicit and specific**. Two memories
merely being about the same broad topic is NOT a relation — leave `related` out.
* `same_series` requires the same subject and a concrete repeated action/event;
shared broad tags such as health, travel, sport, food, or work are insufficient.
* Omit the field entirely when there is nothing to link. Zero links is the normal case.
* Never use `related` as a substitute for UPDATE / SUPERSEDE.
---
{few_shot_section}
###################
Input Data
###################
Current date: {current_date}
## Existing memories
{existing_memories}
## New memories
{new_memories}
---
# Output
Return ONLY a JSON array inside a fenced ```json code block.
If no changes are needed:
```json
[]
```
Now produce the JSON array."""
RECONCILE_STRUCTURE_METADATA_PROMPT = """
Additional structural output contract:
For every ADD, UPDATE, or SUPERSEDE result, preserve or refine `topic_paths`,
`event_facet`, and `topic_assignments`. `event_facet` contains only `subject`,
`participants`, `action`, `objects`, `locations`, and `state`.
`topic_assignments` contains subject-specific `{subject, role, path}` entries;
each path segment is `{name, type}` and type is `topic|facet|series|episode`.
Types follow `topic -> facet -> series -> episode`; levels may be skipped but
types may not repeat or move backward.
Use Series only for repeated occurrences of one concrete activity/state line and
Episode only for one bounded real-world occurrence. Never infer these types from
depth, a broad shared tag, or unrelated co-occurrence. When an Existing memory
already has a semantically equivalent subject-specific path, reuse its exact node
names and types instead of creating a synonym fork. To request exact reuse, emit
`structure_references: [{"memory_id":"<existing_id>", "subject":"...",
"through":"topic|facet|series|episode"}]`. The referenced ID must appear in
Existing memories. `through` means copy that existing path prefix through the
named structural type; an explicitly authored deeper suffix remains new. Reuse
is forbidden when the subject, concrete activity/state line, or Episode boundary
differs. These fields describe the memory, never a future query form such as
"when", "how many", or "who".
Series is a reconcile-time relation: establish it only when the new memory and
at least one independent Existing memory are distinct occurrences of the same
concrete activity/state line. Multiple propositions from the current write do
not prove repetition.
"""
RECONCILE_FEW_SHOT = """
###################
Examples
###################
## Example 1 — One new memory: part conflicts with an existing chain, part is unrelated
Existing memories:
[
{"memory_id": "m1", "content": "The user works as a product manager at Stripe.", "owner": "user", "history_versions": [{"content": "The user works as a product manager at a fintech startup."}]}
]
New memories:
[
{"content": "The user has left Stripe and is now a product lead at Notion, and has recently taken up pottery on weekends.", "owner": "user"}
]
Output:
```json
[
{"op": "SUPERSEDE", "memory_id": "m1", "content": "The user works as a product lead at Notion.", "supersede_reason": "employer changed from Stripe to Notion", "owner": "user", "tags": ["work"]},
{"op": "ADD", "content": "The user has recently taken up pottery on weekends.", "owner": "user", "tags": ["hobby"]}
]
```
A SINGLE new memory carries two things: the job change (same dimension as the existing employment chain m1, which already has history → SUPERSEDE, becomes the new chain head) and an unrelated detail (pottery → ADD). Split the one memory into the right op per part — the new head describes ONLY the employment state, never the pottery.
## Example 2 — Fold two unlinked existing memories + the new change into one 3-node chain
Existing memories:
[
{"memory_id": "a1", "content": "The user is learning to play the guitar.", "owner": "user", "memory_at": "2024-02-10 09:00"},
{"memory_id": "a2", "content": "The user can now play a few full songs on the guitar.", "owner": "user", "memory_at": "2024-09-05 09:00"}
]
New memories:
[
{"content": "The user now performs guitar at local open-mic nights.", "owner": "user"}
]
Output:
```json
[
{"op": "SUPERSEDE", "memory_ids": ["a1", "a2"], "content": "The user performs guitar at local open-mic nights.", "supersede_reason": "guitar skill progressed: learning → playing songs → performing live", "owner": "user", "tags": ["music", "hobby"]}
]
```
a1 and a2 were never linked, but they track the same dimension (guitar progress) and the new memory is the latest step. Fold them with `memory_ids` ordered oldest→newest; they become a 3-node chain (a1 ← a2 ← new head). The head `content` describes ONLY the current state — it does not restate "learning" or "a few songs".
## Example 3 — New head must NOT copy the old memory's content
Existing memories:
[
{"memory_id": "c1", "content": "The user lives in a rented one-bedroom apartment in Shibuya, Tokyo, pays ¥180,000/month, loves the 5-minute walk to the station, but complains the place is noisy at night and has no parking.", "owner": "user"}
]
New memories:
[
{"content": "The user just bought and moved into a three-bedroom house in the suburbs of Yokohama with a garden and a two-car garage.", "owner": "user"}
]
Output:
```json
[
{"op": "SUPERSEDE", "memory_id": "c1", "content": "The user bought and moved into a three-bedroom house in suburban Yokohama, with a garden and a two-car garage.", "supersede_reason": "residence changed from a rented Shibuya apartment to an owned Yokohama house", "owner": "user", "tags": ["housing"]}
]
```
The new memory SUPERSEDEs c1 and becomes the chain head. The head `content` describes ONLY the new Yokohama house. Do NOT drag the old apartment's distinctive details (Shibuya, ¥180,000 rent, the station walk, the noise/parking complaints) into the new node — those clearly belong to the old residence, not the new one. They stay in chain history and remain retrievable; copying them would make the head self-contradictory (renting in Shibuya AND owning in Yokohama at once).
## Example 4 — Different subjects are never merged, however similar the wording
Existing memories:
[
{"memory_id": "d1", "content": "The user drinks black coffee every morning.", "owner": "user"}
]
New memories:
[
{"content": "John drinks black coffee every morning.", "owner": "user"},
{"content": "The user now drinks black coffee twice a day, morning and after lunch.", "owner": "user"}
]
Output:
```json
[
{"op": "ADD", "content": "John drinks black coffee every morning.", "owner": "user", "tags": ["food"]},
{"op": "UPDATE", "memory_id": "d1", "content": "The user drinks black coffee twice a day, in the morning and after lunch.", "owner": "user", "tags": ["food"]}
]
```
The two new memories are near-identical in wording but have DIFFERENT subjects. "John ..." is about another person, so it is a plain ADD — never an UPDATE or SUPERSEDE of d1, even though d1 says almost the same thing about the user. Only the memory whose subject is also "The user" may merge with d1.
## Example 5 — Recurring event: bump count and keep a timeline (UPDATE, not ADD)
Existing memories:
[
{"memory_id": "i1", "content": "John injured his ankle last season and needed time off plus physical therapy.", "owner": "user", "memory_at": "2023-05-21 19:48"}
]
New memories:
[
{"content": "John twisted his ankle again during a recent game.", "owner": "user", "memory_at": "2023-10-13 13:50"}
]
Output:
```json
[
{"op": "UPDATE", "memory_id": "i1", "content": "John has injured his ankle two times: first last season (around the 2022–2023 season), needing time off and physical therapy; second during a game around 2023-10-13.", "owner": "user", "tags": ["health", "basketball", "injury"]}
]
```
Same event type happening again → UPDATE the old memory. State the **count** and list occurrences with times when known. Do NOT ADD a second "John injured his ankle" fact, and do NOT SUPERSEDE (the first injury did not stop being true).
"""
# ================================================================
# Working / coding memory (coding/)
# ================================================================
CODING_EXTRACT_PROMPT = """\
You extract durable WORKING memories from a task-execution session segment.
A "working" session is any hands-on task where the user operates a computer /
system through tools — this covers BOTH software/code work (writing, debugging,
refactoring, building, deploying, API/library usage) AND system & operations
work (running shell/PowerShell commands, disk/storage analysis or cleanup, file
& folder operations, installing/configuring software, environment/device setup,
hardware/disk health inspection, backup, scheduling/automation, data processing).
You produce TWO layers from the SAME segment:
EXPERIENCES (procedural, "how to do it") — reusable method, extracted only
when it clears the VALUE BAR below. A (task, search_keys, solution,
boundary_envs) bundle.
FACT (episodic, "what happened this time") — EXACTLY ONE record of THIS
session: what the user wanted, how it was gone about, how it came out. No
value bar, but there is never more than one.
The two layers answer different questions and are stored separately. A method
that generalizes goes in EXPERIENCES; the story of this particular session —
only true for this system / this project / right now — goes in the FACT.
═══════════════════════════════════════════════════════════════════
VALUE BAR (EXPERIENCES ONLY) — DO NOT cross this lightly.
═══════════════════════════════════════════════════════════════════
A memory MUST satisfy ALL of:
(a) Solves a problem likely to recur for the same user
(b) Solution is non-trivial: combines multiple pieces of info / steps, OR
carries a decision rationale, OR captures a hard-won workaround, OR is a
reusable procedure/recipe for an operational task (e.g. how to analyze &
clean up disk usage, how to batch-organize files, how to inspect hardware
health, how to set up a scheduled job)
(c) A future agent / user would prefer looking it up over re-discovering it
IMPORTANT — analysis / diagnostic / investigation tasks COUNT:
A task does NOT have to make a change or "fix" something to be worth remembering.
Read-only / analytical / troubleshooting work is valuable when its PROCEDURE or
CONCLUSION is reusable. Explicitly extract:
- Analysis / diagnostic recipes: the reusable procedure + what it revealed
(e.g. "how to analyze which folders eat C-drive space" — the scan commands,
the gotchas hit, and the finding). "It only produced a report, no change"
is NOT a reason to skip — the reusable METHOD is the memory.
- Investigation outcomes, INCLUDING negative / not-found results
(e.g. "searched folder X across C: and D: with rg / fs_search / SQL index —
not found anywhere"). A recorded dead-end saves the next agent from repeating
the same fruitless search, so it clears the bar.
Do NOT reject a memory merely because "the assistant could regenerate the
commands" or "nothing was actually modified". If the procedure or the conclusion
would save future effort, keep it.
DO NOT extract:
- Truly trivial single-step ops with no reusable procedure ("rename one file",
"git status", "run ls once")
- One-off issues already resolved with no transferable lesson
- Information that is already in the repo/system (don't duplicate package.json)
- Per-PR / per-session throwaway tasks unless they encode a reusable pattern
- The act of ASKING THE USER for missing information. Requesting a parameter,
listing what is unknown, or eliciting required fields is conversational
behaviour, not a method: every agent already knows how to ask a question, so
there is nothing to look up later. Never emit an experience whose task or
search_keys are shaped like "how to ask for X", "gather the missing Y",
"eliciting required fields", "what to clarify before starting". Note this
stays out of EXPERIENCES only — that the session ended waiting on the user
is real state and belongs in the FACT.
If the segment made no substantive progress because it stalled waiting on the
user — the assistant only asked for parameters, or was blocked before doing any
real work — emit "experiences": [] and write the FACT alone. Nothing was
learned yet, so there is no method to store. This matters more than it looks:
an experience distilled from a stall reads as a legitimate skill on retrieval,
and a later segment of the same task will match it and ask the user again
instead of proceeding, so the stall reproduces itself for the rest of the
session. Judge substance by what the tools actually did, not by whether the
turn produced fluent text.
Heuristic: if the user (or a future agent doing the same kind of task) would
want this written down instead of figuring it out again, keep it. A multi-step
operational procedure (even a system/ops or read-only analysis/search one, not
just code) usually qualifies. Only drop it if it is a single command any user
would find via `--help` in 30 seconds AND carries no decision/gotcha worth
remembering.
═══════════════════════════════════════════════════════════════════
EXPERIENCE FIELDS
═══════════════════════════════════════════════════════════════════
task — Query-shaped name; phrase as how a user would later ASK.
"publish a release to the xx platform" not
"publish_release_internal"
Self-contained (no "this", "the file", "we").
search_keys — 0 to 5 alternative phrasings capturing important aspects of
solution that `task` alone wouldn't match. Phrase as questions
or topics future users would ASK ABOUT. Distinct from task.
Omit if task already covers everything.
solution — Complete, self-contained content the user actually needs:
commands, credential locations (NOT credential VALUES),
file paths, ordered steps, gotchas, decision rationale.
Aggregate multi-turn end-to-end work into ONE solution if it
forms a coherent task; split into multiple memories only when
the user did unrelated tasks.
boundary_envs — Concrete pinning that solution depends on, multi-line "key: value":
runtime / SDK / library + version
API endpoint or method signature
external service / middleware version
env var NAME (not value)
config file path
host / cluster / region identifier
If solution is environment-agnostic, leave as "" (empty).
Purpose: when the underlying SDK/middleware upgrades, future
consumers can inspect this to decide if solution still applies.
confidence — 0.0 to 1.0
1.0 user explicitly stated this as a rule / instruction
0.7 inferred with good evidence from tool actions
0.5 weakly inferred; one observation; uncertain
═══════════════════════════════════════════════════════════════════
FACT FIELD — EXACTLY ONE per segment, no value bar
═══════════════════════════════════════════════════════════════════
You write ONE fact for the whole segment — a recoverable state record for this
session, not a polished retrospective and never a list of separate findings. A
future agent must be able to resume from it without mistaking a suggestion,
inference, or partial search for something verified.
subject — WHO or WHAT this session is about: the single entity the work was
performed on or for. One short noun phrase, no sentence, no verb.
Pick the most specific entity that the session is *about*, not the
tool it used:
a person's name when the session serves one named person
("Dylan", "Matthew")
the file / module / symbol when the work targets code
("auth/middleware.ts", "parse_plan_text")
the service / host / device / drive for ops work
("D:\\WXWork", "redis-cache-prod", "ci.yml")
This is a HARD KEY, not prose: two sessions about different people
or different files MUST get different subjects, and two sessions
about the same one MUST get the same string, spelled identically.
Prefer the exact identifier as it appears in the trace over a
description of it. If the segment genuinely has no single subject,
output "".
Why it matters: subject is the gate that decides whether a later
session is allowed to overwrite this one as "the same task,
continued". Get it wrong and one person's state silently replaces
another's.
statement — A compact record of THIS session. Two parts, and the second is the
one that gets lost.
Preserve task-scoped givens and concrete constraints instead of
replacing them with a generic evaluation of the outcome. State
each rule once: repeated instruction text competes with the data
the memory must retain.
PART 1 — what happened, about 300 latin characters (~150 CJK).
Start with the specific goal and object ("reclaim disk space taken
by D:\\WXWork", "locate the new_file_search folder"), not a generic
"fix a bug" / "find a file". Then roughly what was inspected, tried,
or changed. Then how it ended, with the semantics of exactly one of:
completed, partially completed, failed, blocked, or
still awaiting verification. Include negative results and dead ends
without overstating them.
PART 2 — the TASK-SCOPED GIVENS: everything the WHOLE task runs on,
even when this segment did not establish it and even when it feels
too elementary to state. Bindings (what each name/symbol/label
stands for), fixed values the task supplies rather than derives,
conventions adopted (indexing base, units, timezone, defaults), and
this segment's own result WITH its value ("x = 7", not "the value
was computed").
Write PART 2 as data, one item per line, values as given:
retry budget = 5; timeout 30s; index starts at 0
step 3 input: n=64, tolerance 1e-6
slot B: type=private, cost 688.7-721.3 per unit
A line should be almost entirely values. Keep the trace's notation —
symbols, units, numbers, spellings — and drop its sentences: strip
the phrases that announce a given rather than being one ("I would
prefer", "it should be somewhere between … and …"). They repeat once
per item, so a 15-item list pays for them 15 times.
Carry them on EVERY fact; never write "as above" or "same as the
previous session" — retrieval returns a handful of facts by
similarity and the referent may not be among them.
Aim under 1200 characters total. If PART 2 does not fit, fix the
notation, never the coverage: statement is the retrieval vector AND
the only field a later segment can read (details is neither embedded
nor injected), so anything cut from it is gone. Compress the wording
around a given; never the given.
The test: if the next segment cannot see the original material, can
it continue from this record alone? Only a yes counts. A missing
binding does not fail loudly — the name still looks meaningful, so
it gets read with its general-usage meaning and the answer that
follows is fluent and about a different question.
LANGUAGE — same language as the session trace: statement is the
retrieval vector and queries arrive in the user's working language,
so another language pays a cross-lingual penalty on every lookup.
EVIDENCE FIDELITY — record only actions actually executed and
evidence explicitly present in the trace. Preserve material
commands, flags, arguments, errors, and numbers verbatim.
NEVER complete a command with customary flags, and never turn
advice or plans into actions:
"ran Get-ChildItem -Force" → "ran Get-ChildItem"
(the trace did not contain `-Force`; do not invent it)
Scope every negative finding to what was actually searched, and
enumerate EVERY explicit coverage limit that could invalidate it,
especially permission failures and depth limits: keep the roots,
filters, and depth that were in effect ("not found under C:\\ and
D:\\ at depth 3; some directories were not covered due to Access
denied"), never widen it to "does not exist". Do not omit one limit
to make the record prettier.
Never promote a partial search to global absence.
A blocked attempt keeps its blocker.
Preserve the distinction among attempted, observed, verified,
inferred, and unresolved — express it naturally ("tried … but was
blocked by Access denied", "suspected …, not yet verified"), not as
labels. Without tool output or explicit confirmation, NEVER claim a
fix succeeded, a cause was established, or a path does not exist.
Do NOT record greetings or pure chatter — that is the only case
getting "fact": null. A segment that merely stated requirements, or
stalled before doing work, still established something.
details — The session's concrete OUTPUT, copied over verbatim. `statement` is
a summary and a summary necessarily drops the individual values; a
later lookup almost always wants one specific value, not the gist.
So whatever addressable result this session produced, reproduce it
here in its original form and structure:
a plan / schedule / itinerary → keep every line, every day, every
slot, and every price exactly as written
a config or code block → keep the block
a command's material output → keep the lines that carry the
findings (sizes, counts, versions, error text)
a structured result (table, list of records) → keep the rows
Preserve the original layout — line breaks, `Day 1:` / key: value
prefixes, units, currency symbols, ids. Do not re-summarize, do not
reorder, do not round numbers, do not translate.
Copy ONLY what is present in the trace. Never fill a gap: if a slot
was left unfilled, leave it unfilled or say so — an invented value
is worse than a missing one, because it will be retrieved and used.
Cap around 4000 characters; if the output is longer, keep the parts
carrying concrete values and drop decorative prose, noting what was
dropped.
"" when the session produced no such artifact (pure investigation
with no findings yet, or a stall waiting on the user).
This field is NOT part of the retrieval vector — `statement` and
`subject` do the finding, `details` supplies the value once found.
So do not write retrieval bait here, and do not duplicate the
summary; write the raw thing.
files — 0..N absolute or repo-relative FILE OR DIRECTORY paths actually
operated on or investigated during this session. Keep investigated
roots such as `C:\\` and `D:\\`. Only include paths that were
actually accessed/searched, not paths merely suggested or planned;
[] if none apply.
═══════════════════════════════════════════════════════════════════
HOST CONTEXT (given by SDK)
═══════════════════════════════════════════════════════════════════
- workspace_id present: {workspace_present}
- branch present: {branch_present}
When no workspace_id is present, the solution cannot rely on unstated
project-local context — spell out any repo / path / service it needs inside
`solution` or `boundary_envs` so the memory stays usable on its own.
═══════════════════════════════════════════════════════════════════
CONVERSATION
═══════════════════════════════════════════════════════════════════
{conversation_block}
═══════════════════════════════════════════════════════════════════
OUTPUT (strict JSON only, no markdown)
═══════════════════════════════════════════════════════════════════
{{
"experiences": [
{{
"task": "...",
"search_keys": ["...", "..."],
"solution": "...",
"boundary_envs": "...",
"confidence": 0.9
}}
],
"fact": {{
"subject": "...",
"statement": "...",
"files": ["..."],
"details": "..."
}}
}}
DEFAULT TO EXTRACTING. When the segment did real work, you MUST output at least
one experience — even if the task looks simple or was a one-shot success, still
capture the reusable essence of what was done (the method / command / how-to).
When the session is thin, the single most valuable experience is enough.
The ONE exception is the stall case above: if the segment did no real work
because it was blocked asking the user for missing information, output
"experiences": [] and write only the fact. Do not manufacture an experience to
satisfy this instruction — a fabricated one ("how to ask for the departure
city") is worse than none, because it is later retrieved and followed. Empty is
allowed ONLY for that reason, never as a shortcut when work was actually done.
"fact" is a SINGLE object, never a list — one record per session.
ALWAYS write the fact. The bar for the fact is not "was this valuable" but "did
anything at all happen", and a segment reaching you has already cleared that.
This includes segments that look like setup rather than work: a single
user message stating requirements, parameters, or a starting state, with no
tools run, is exactly the material a later session cannot reconstruct on its own
— 把需求、参数、起始状态连同情形一起写进 `statement`(紧凑记法,不要照抄对话原句),
不要推给 `details`(`details` 不进检索向量,读取侧也不再注入它)。Use
"fact": null ONLY for greetings and pure chatter with no content whatsoever.
When in doubt, write the fact: an unneeded record costs one row, while a dropped
one costs every later session that needed it and has no way to know it existed.
"""
CODING_EXTRACT_PROMPT_CODING = """\
╔═══════════════════════════════════════════════════════════════════════════╗
║ HARD CONSTRAINT — read this before anything else, it overrides any ║
║ phrasing later in this prompt that sounds like it wants a narrative. ║
║ ║
║ You are NOT writing a report of what a person did. You are writing what a ║
║ FUTURE AGENT needs in order to act. That agent already has its own task; ║
║ it does not care what this user wanted. It needs to know: what was decided ║
║ here, at which code coordinates, what was tried and rejected, and whether ║
║ it was verified. ║
║ ║
║ Concretely, in every `statement` and every `solution`: ║
║ · The grammatical subject is the CODE or THE DECISION — never "the user" ║
║ and never "the agent". Do not write "The user attempted to…", ║
║ "The user wanted…", "The agent then checked…". ║
║ · Never close with a bookkeeping phrase like "the session was completed", ║
║ "the error was noted", "no solution was found yet". Close with the ║
║ decision and its verification status. If nothing was decided, close ║
║ with what was RULED OUT and the next unexplored lead — that is the ║
║ reusable part. ║
║ · Keep the concrete symptom (error string, failing command, wrong ║
║ output) — that is what a future search will match on. ║
║ ║
║ bad: "The user attempted to write a QTable to RST with header_rows and ║
║ hit a TypeError. The session was completed with the error noted." ║
║ good: "ascii.rst's RST writer rejects header_rows: `tbl.write(..., \\ ║
║ format='ascii.rst', header_rows=['name','unit'])` raises TypeError ║
║ because RST.__init__ does not forward it, unlike ascii.fixed_width ║
║ which accepts it. No fix written — the writer needs the same ║
║ header_rows plumbing as fixed_width." ║
╚═══════════════════════════════════════════════════════════════════════════╝
You extract durable WORKING memories from a task-execution session segment.
A "working" session is any hands-on task where the user operates a computer /
system through tools — this covers BOTH software/code work (writing, debugging,
refactoring, building, deploying, API/library usage) AND system & operations
work (running shell/PowerShell commands, disk/storage analysis or cleanup, file
& folder operations, installing/configuring software, environment/device setup,
hardware/disk health inspection, backup, scheduling/automation, data processing).
You produce TWO layers from the SAME segment:
EXPERIENCES (procedural, "how to do it") — reusable method, extracted only
when it clears the VALUE BAR below. A (task, search_keys, solution,
boundary_envs) bundle.
FACT (episodic, "what was established this time") — EXACTLY ONE record of THIS
session. It is NOT a narrative of what the user did. It records, in this order:
the task and its concrete symptom (this is what makes it findable later), the
code coordinates, WHAT WAS SETTLED ON and what was rejected, and how far it was
verified. No value bar, but there is never more than one.
The two layers answer different questions and are stored separately. A method
that generalizes goes in EXPERIENCES; the story of this particular session —
only true for this system / this project / right now — goes in the FACT.
═══════════════════════════════════════════════════════════════════
VALUE BAR (EXPERIENCES ONLY) — DO NOT cross this lightly.
═══════════════════════════════════════════════════════════════════
A memory MUST satisfy ALL of:
(a) Solves a problem likely to recur for the same user
(b) Solution is non-trivial: combines multiple pieces of info / steps, OR
carries a decision rationale, OR captures a hard-won workaround, OR is a
reusable procedure/recipe for an operational task (e.g. how to analyze &
clean up disk usage, how to batch-organize files, how to inspect hardware
health, how to set up a scheduled job)
(c) A future agent / user would prefer looking it up over re-discovering it
IMPORTANT — analysis / diagnostic / investigation tasks COUNT:
A task does NOT have to make a change or "fix" something to be worth remembering.
Read-only / analytical / troubleshooting work is valuable when its PROCEDURE or
CONCLUSION is reusable. Explicitly extract:
- Analysis / diagnostic recipes: the reusable procedure + what it revealed
(e.g. "how to analyze which folders eat C-drive space" — the scan commands,
the gotchas hit, and the finding). "It only produced a report, no change"
is NOT a reason to skip — the reusable METHOD is the memory.
- Investigation outcomes, INCLUDING negative / not-found results
(e.g. "searched folder X across C: and D: with rg / fs_search / SQL index —
not found anywhere"). A recorded dead-end saves the next agent from repeating
the same fruitless search, so it clears the bar.
Do NOT reject a memory merely because "the assistant could regenerate the
commands" or "nothing was actually modified". If the procedure or the conclusion
would save future effort, keep it.
DO NOT extract:
- Truly trivial single-step ops with no reusable procedure ("rename one file",
"git status", "run ls once")
- One-off issues already resolved with no transferable lesson
- Information that is already in the repo/system (don't duplicate package.json)
- Per-PR / per-session throwaway tasks unless they encode a reusable pattern
Heuristic: if the user (or a future agent doing the same kind of task) would
want this written down instead of figuring it out again, keep it. A multi-step
operational procedure (even a system/ops or read-only analysis/search one, not
just code) usually qualifies. Only drop it if it is a single command any user
would find via `--help` in 30 seconds AND carries no decision/gotcha worth
remembering.
═══════════════════════════════════════════════════════════════════
CODE WORK — what a future agent cannot re-derive, so it MUST be captured
═══════════════════════════════════════════════════════════════════
When the segment is software work on a repository, the reusable method is only
half of it. The other half is WHERE the work lives and WHICH VERSION won. Those
cannot be recovered by reading the code later — the code shows the current state,
not which branch it sits on, nor what was already rejected.
This section does NOT ask for a longer record. It asks you to SPEND THE SAME
BUDGET on higher-value tokens: drop restated goals, narration of what the
assistant "then decided to", and pasted code you are only echoing; spend those
characters on the four items below instead. A record that omits the branch but
quotes 30 lines of diff is a bad trade.
1. BRANCH / WORKING LOCATION — verbatim, in the fact `statement`. If the segment
mentions or checks out `feat/x` / `fix/y`, that token MUST appear. An agent
that resumes on the wrong branch produces changes that are silently thrown
away, so this one token outranks every other detail.
2. WHICH VERSION WON — when the user changed their mind, name the winner and mark
the loser in one clause: "settled on A; the interim B was dropped". Not both in
full — one clause. Keeping only the latest is wrong: an agent that meets the
rejected code must recognize it, or it will "fix" things back to the discarded
variant. Phrases like `scratch that / go back to / actually, let's / no, not that`
mark this: what follows is of record, what precedes is superseded.
3. EXACT COORDINATES, NOT PASTED CODE — repo-relative path
(`qutebrowser/utils/debug.py`, never bare `debug.py`) plus the function /
class touched. If a signature changed, give the new signature. If a constant,
regex or literal was decided, quote THAT verbatim — but quote only it, not the
surrounding block. One accurate line beats a paragraph of approximation.
4. VERIFIED vs MERELY WRITTEN — four words is enough ("tests run and passing" /
"confirmed by git diff only, tests not run"). "the edit is in" and "the edit is in
and tested" lead a future agent to do very different things next.
Anti-pattern — summarizing the goal and dropping the coordinates:
bad: "refactored the colour parsing logic to be cleaner."
good: "on feat/q4, in qutebrowser/config/configtypes.py QtColor._parse_value:
settled on `mult = 360.0 if kind == 'h' else 255.0`; the interim version that
used 255.0 for everything was dropped. Tests not run."
(the good one is SHORTER than most bad ones — it just spends every character
on something that cannot be re-derived)
═══════════════════════════════════════════════════════════════════
EXPERIENCE FIELDS
═══════════════════════════════════════════════════════════════════
task — Query-shaped name; phrase as how a user would later ASK.
"publish a release to the xx platform", not "publish_release_internal"
Self-contained (no "this", "the file", "we").
search_keys — 0 to 5 alternative phrasings capturing important aspects of
solution that `task` alone wouldn't match. Phrase as questions
or topics future users would ASK ABOUT. Distinct from task.
Omit if task already covers everything.
solution — Complete, self-contained content the user actually needs:
commands, credential locations (NOT credential VALUES),
file paths, ordered steps, gotchas, decision rationale.
Aggregate multi-turn end-to-end work into ONE solution if it
forms a coherent task; split into multiple memories only when
the user did unrelated tasks.
boundary_envs — Concrete pinning that solution depends on, multi-line "key: value":
runtime / SDK / library + version
API endpoint or method signature
external service / middleware version
env var NAME (not value)
config file path
host / cluster / region identifier
If solution is environment-agnostic, leave as "" (empty).
Purpose: when the underlying SDK/middleware upgrades, future
consumers can inspect this to decide if solution still applies.
confidence — 0.0 to 1.0
1.0 user explicitly stated this as a rule / instruction
0.7 inferred with good evidence from tool actions
0.5 weakly inferred; one observation; uncertain
═══════════════════════════════════════════════════════════════════
FACT FIELD — EXACTLY ONE per segment, no value bar
═══════════════════════════════════════════════════════════════════
You write ONE fact for the whole segment — a recoverable state record for this
session, not a polished retrospective and never a list of separate findings. A
future agent must be able to resume from it without mistaking a suggestion,
inference, or partial search for something verified.
statement — A compact summary of THIS session. It has two jobs at once, and it
must do BOTH: it is the retrieval vector (a future agent finds it by
searching with a fresh issue report, phrased the way a user phrases a
problem), and it is the payload that agent then acts on. So:
OPEN with the task and the symptom, in the words a user would use.
This is what makes it findable — keep the surface form close to how
the problem would be reported. Name the specific goal and object
("reclaim disk space under D:\\WXWork", "adjust the hue multiplier
in QtColor._parse_value on feat/q4", "locate the new_file_search
folder"), never a generic "fix a bug" / "look for a file". Include
the concrete symptom: the error string, the failing command, the
wrong output.
THEN give the coordinates and the decision.
repo-relative path, symbol, and branch for code work. State what was
SETTLED ON, and in the same clause mark what was dropped
("settled on X; the interim Y was dropped"). This is the part that
cannot be re-derived by reading the code — the code only shows the
winner, never that Y was tried and rejected. An agent that meets the
rejected variant later must be able to recognize it, or it will
"fix" the code back to the discarded version.
CLOSE with the evidence status.
Exactly one of: completed, partially completed, failed, blocked, or
still awaiting verification — plus how it was checked ("tests run and
passing" / "confirmed by git diff only, tests not run"). Include
negative results and dead ends without overstating them.
⚠️ Never stop at the narrative. "The user tried X and hit error Y; no
solution found yet" is a wasted record: it retrieves fine but gives a
future agent nothing to act on. If the session genuinely reached no
decision, say what was ruled out and what the next unexplored lead is —
that is the reusable part.
The subject of the sentences should be the code and the decision, not
"the user". 2 to 4 sentences, flowing prose, not labelled sections.
Hard ceiling ~400 latin characters (~200 CJK characters).
EVIDENCE FIDELITY — mandatory:
- Record only actions actually executed and evidence explicitly
present in the trace. Preserve material commands, flags,
arguments, errors, and numbers verbatim. NEVER complete a
command with customary flags or turn advice/plans into actions.
- Preserve the distinction among attempted, observed, verified,
inferred, and unresolved. Do not add those labels to the prose;
express their meaning naturally, e.g. "tried … but was blocked by
Access denied" or "suspect … , not yet verified".
- Without tool output or explicit confirmation, NEVER claim a fix
succeeded, a cause was established, a file/folder does not
exist, or tests passed.
- Scope every negative finding to what was actually searched and
enumerate EVERY explicit coverage limit that could invalidate
it, especially permission failures and maximum search depth:
"not found within the searched parts of C:\\ and D:\\; some
directories were skipped on Access denied, and the search was also
depth-limited". Do not omit one limit to make the
summary prettier. Never promote a partial search to global
absence.
- State causality as certain only when the trace directly verifies
it. Otherwise retain the hedge ("suspected" / "inferred" / "not yet
verified"); do not rewrite an
incorrect test assumption as the system's root cause.
CRITICAL — this text IS the retrieval vector, and it is the only
thing a future query gets matched against. A generic summary is
worthless, because EVERY session looks like a generic summary: it
would match every query and answer none of them. So you MUST name
the concrete anchors that make THIS session findable — real file
paths, function / symbol names, branch names, commands, error
strings, versions, config keys and values, host / service names.
If someone later searches for any one of those specifics, this
summary has to be the thing that comes back.
good: "fixing a failing `npm run build` in CI. Read
.github/workflows/ci.yml and package.json; traced it to webpack
5.74 hitting ERR_OSSL_EVP_UNSUPPORTED under node 18. Bumping the
workflow's node to 20 made the build pass; node 18 still
reproduces locally."
good: "on branch feat/q0, in qutebrowser/utils/debug.py signal_name:
PyQt<5.11 only exposes the name via repr(sig), and repr comes in
both a bracketed and a parenthesised form. Settled on two patterns
tried in order with fullmatch, breaking on the first hit; the
single-regex version was dropped. Confirmed by git diff, tests not
run."
bad: "user wanted a build bug fixed, the agent looked at some config
files, changed some code, and fixed it."
(no path, no command, no error string, no version — nothing
to retrieve on; every session reads exactly like this)
bad: "fixed the signal-name parsing compatibility issue on a feature
branch."
(which branch? which file? which function? which of the two
competing versions won? nothing here survives being
retrieved a week later)
Fidelity examples (bad → good):
"ran Get-ChildItem -Force" → "ran Get-ChildItem"
(the trace did not contain `-Force`; do not invent it)
"confirmed new_file_search does not exist" →
"new_file_search was not found within the searched parts of C:\\ and
D:\\; some directories were skipped on Access denied or by the depth
limit, so the investigation is only partly done"
"identical payloads cause a spurious lock error" →
"the verification script assumed the last writer is always a fixed
rank; that assumption does not hold, so whether there is a lock bug
is still unverified"
Do NOT record greetings or pure chatter. If the segment established
nothing at all, output "fact": null.
files — 0..N absolute or repo-relative FILE OR DIRECTORY paths actually
operated on or investigated during this session. Keep investigated
roots such as `C:\\` and `D:\\`. Only include paths that were
actually accessed/searched, not paths merely suggested or planned;
[] if none apply.
═══════════════════════════════════════════════════════════════════
HOST CONTEXT (given by SDK)
═══════════════════════════════════════════════════════════════════
- workspace_id present: {workspace_present}
- branch present: {branch_present}
When no workspace_id is present, the solution cannot rely on unstated
project-local context — spell out any repo / path / service it needs inside
`solution` or `boundary_envs` so the memory stays usable on its own.
═══════════════════════════════════════════════════════════════════
CONVERSATION
═══════════════════════════════════════════════════════════════════
{conversation_block}
═══════════════════════════════════════════════════════════════════
OUTPUT (strict JSON only, no markdown)
═══════════════════════════════════════════════════════════════════
{{
"experiences": [
{{
"task": "...",
"search_keys": ["...", "..."],
"solution": "...",
"boundary_envs": "...",
"confidence": 0.9
}}
],
"fact": {{
"statement": "...",
"files": ["..."]
}}
}}
MANDATORY: You MUST output at least ONE experience. Do NOT return an empty list.
Even if the task looks simple or is a one-shot success, still capture the
reusable essence of what was done (the method / command / how-to). Only the
single most valuable experience is required when the session is thin — but never
output an empty "experiences" array.
"fact" is a SINGLE object, never a list — one record per session. Use
"fact": null only when the segment is pure chatter that established nothing.
"""
CODING_RECONCILE_PROMPT = """\
You decide how to merge new coding memory drafts with the user's existing memories.
For each new draft, decide one of:
ADD — no existing memory describes the same task/aspect
UPDATE — an existing memory describes the same task; the new draft replaces it
(solution may have changed, env may have upgraded, etc.)
SKIP — duplicate of existing; nothing new
Additionally, you may emit DELETE ops on existing candidates when the new
content makes them obsolete or invalid:
DELETE — the new conversation EXPLICITLY supersedes/cancels/invalidates an
existing memory that cannot be repaired via UPDATE. Examples:
* user says "don't use X anymore" and X is only present as a
separate memory (not the one being added/updated)
* candidate's boundary_envs (service / endpoint / SDK) is gone
and the new conversation confirms the deprecation
Rules:
- "Same task" means the existing memory and the new draft would both be valid
answers to the same future user query.
- If solution differs, prefer UPDATE (we don't keep history).
- If both task and solution match closely, SKIP.
- DELETE only when there is EXPLICIT evidence the candidate is invalid/obsolete.
Never DELETE just because a candidate looks "stale".
- Output ops can outnumber drafts when DELETEs are produced.
═══════════════════════════════════════════════════════════════════
NEW DRAFTS
═══════════════════════════════════════════════════════════════════
{drafts_block}
═══════════════════════════════════════════════════════════════════
EXISTING CANDIDATES (per draft, retrieved by task similarity)
═══════════════════════════════════════════════════════════════════
{candidates_block}
═══════════════════════════════════════════════════════════════════
OUTPUT (strict JSON only, no markdown)
═══════════════════════════════════════════════════════════════════
{{
"ops": [
{{"draft_idx": 0, "action": "ADD"}},
{{"draft_idx": 1, "action": "UPDATE", "target_experience_id": "exp_xxx", "reason": "..."}},
{{"draft_idx": 1, "action": "DELETE", "target_experience_id": "exp_yyy", "reason": "user explicitly deprecated Y"}},
{{"draft_idx": 2, "action": "SKIP", "reason": "duplicate"}}
]
}}
If there are no drafts, output {{"ops": []}}.
"""
CODING_FACT_RECONCILE_PROMPT = """\
You maintain session-level WORKING FACTS. Each statement summarizes one whole
trace/session: the user's task, the rough approach, and the result. It is not an
atomic claim.
Every new session fact below WILL be stored; that is not your decision. Your only
job is to detect task continuation: is the new session continuing to advance the
SAME user task as an existing session fact? If so, SUPERSEDE the older fact so
the links form a cross-session progress chain for that task.
Mark an existing fact as superseded ONLY when there is strong evidence that:
- The subjects match: both records are about the SAME entity — the same
person, the same file/module, the same host/service. A shared subject is
necessary but not sufficient; AND
- The user goal is materially the SAME, not merely in the same topic/project;
AND
- The new trace continues, retries, extends, verifies, or finishes the work
recorded by the existing trace.
Do NOT supersede when:
- The two records name different subjects. Two people in one group, two files
in one module, two hosts in one cluster are PARALLEL, not successive: one
person's state is not a newer version of another's. Where a subject is shown
as (unspecified), fall back to judging by the statement alone.
- The sessions merely mention the same file, symbol, component, repository,
error, or technology but pursue different user goals.
- One task only supplies context or a technique for another task.
- The relationship is uncertain. Be conservative: INSERT is the safe default;
merging unrelated sessions destroys their separate task history.
Treat each statement as evidence-bounded text. Do not reinterpret an inference
inside it as an established cause or use a shared conjecture/error as proof that
the user goals are the same. Your decision is only about task continuation; do
not "correct", complete, or embellish either session record.
Examples:
existing: "The user needed to fix CI's npm run build; traced it to
ERR_OSSL_EVP_UNSUPPORTED under Node 18, workflow not yet changed."
new: "The user continued fixing the same CI build; raised Node in
.github/workflows/ci.yml to 20 and npm run build passed."
→ SUPERSEDE
existing: "The user needed to fix JWT expiry handling in
auth/middleware.ts; refresh logic was added."
new: "The user needed performance logging added to
auth/middleware.ts; a latency metric was introduced."
→ keep both
(same file/project, different user goal)
═══════════════════════════════════════════════════════════════════
NEW SESSION FACTS
═══════════════════════════════════════════════════════════════════
{drafts_block}
═══════════════════════════════════════════════════════════════════
EXISTING SESSION FACTS (per new fact, retrieved by statement similarity)
═══════════════════════════════════════════════════════════════════
{candidates_block}
═══════════════════════════════════════════════════════════════════
OUTPUT (strict JSON only, no markdown)
═══════════════════════════════════════════════════════════════════
{{
"ops": [
{{"draft_idx": 0, "supersedes": ["fact_xxx"], "reason": "continues the same CI fix task"}},
{{"draft_idx": 1, "supersedes": []}}
]
}}
Only use fact ids that appear in the candidate list for that same draft_idx.
If nothing is superseded anywhere, output {{"ops": []}}.
"""
CODING_CHAIN_SUMMARY_PROMPT = """\
A user task in a working-memory store has progressed across sessions. Below are
the session summaries in that task's progress chain, oldest first, ending with
the current session.
Summarize how the SAME task progressed across sessions to its current state.
Cover every important stage in chronological order. For each stage, preserve
where available: the actual action or change, its direct result, and any
unresolved issue or limiting condition. End with an explicit current status.
Rules:
- This is compression only. Report ONLY what the entries say; never add or
complete a command, cause, motive, action, test result, success state, or
global negative conclusion.
- Preserve evidence status exactly: attempted is not completed, observed is
not verified, inferred is not established, and unresolved stays unresolved.
Keep uncertainty words and search/permission/coverage limits.
- State the current ending status faithfully (completed, partially completed,
failed, blocked, or still awaiting verification). Never upgrade it based on
a plausible causal story or on a later entry that did not verify it.
- When entries mention a suspected cause and a later result, connect them
causally only if an entry directly says that cause was verified.
- Preserve concrete retrieval anchors: file paths, symbols, commands, error
strings, versions, config keys/values, and important numeric values that
matter to the progress.
- For chains of 2-3 generations, do not over-compress the progression into one
sentence. Prefer 2-4 compact paragraphs when useful, or readable prose with
a clear task goal, stage-by-stage progression, and current status.
- Do not mechanically copy the full history. Omit repetition and low-value
detail while retaining important actions, direct results, and leftovers.
- Do not invent stage headings or dates. Mention a date only when the entries
make it meaningful; bare timestamps are noise.
- If the entries say older revisions were omitted, explicitly state that
earlier history was omitted; do not imply the supplied entries are complete.
- Write in the same language the entries are written in.
- No preamble or enclosing quotes.
Example
entries:
(2026-07-20) The user needed to fix CI's npm run build; traced it to
ERR_OSSL_EVP_UNSUPPORTED under Node 18, workflow not yet
changed.
(2026-07-24) The user continued that CI fix; raised Node in
.github/workflows/ci.yml to 20, but the dependency cache
still reproduced the old error.
(now) The user continued verifying the same fix; after clearing the
npm cache, npm run build passed.
output:
The task goal is fixing CI's npm run build. It was first traced to
ERR_OSSL_EVP_UNSUPPORTED under Node 18, with the workflow not yet changed;
then .github/workflows/ci.yml was raised to Node 20, but the dependency
cache kept the old error present. Finally, after clearing the npm cache,
npm run build passed, so this build fix is currently verified as complete.
═══════════════════════════════════════════════════════════════════
ENTRIES
═══════════════════════════════════════════════════════════════════
{chain_block}
═══════════════════════════════════════════════════════════════════
OUTPUT (the summary itself, nothing else)
═══════════════════════════════════════════════════════════════════
"""
# ================================================================
# Episode / topic summaries
# ================================================================
EPISODE_SUMMARY_PROMPT = """You are summarizing ONE episode of conversation with the user.
An episode is a single stretch of discussion — one thing that was talked about,
possibly across several sessions of writing.
## Memories captured during this episode (oldest → newest)
{memories}
---
Write a single dense summary of what happened / what was discussed in this episode.
Rules:
* 2–4 sentences, third person, starting with "The user ..." (or the named person
if the memories are about someone else).
* Tie the memories together into one coherent account — an episode usually has a
thread running through it (a plan, a problem, an outing, a decision).
* Preserve concrete details: names, places, numbers, dates.
* Do NOT invent anything that is not in the memories.
* Do NOT enumerate the memories one by one; synthesize them.
* Output language must match the language of the memories.
Return ONLY the summary text, no preamble, no JSON, no quotes."""
TOPIC_SUMMARY_PROMPT = """You are summarizing everything known about ONE topic.
## Topic
{topic_name}
## Memories under this topic (oldest → newest)
{memories}
---
Write a single dense summary of what is known about the user with respect to this
topic.
Rules:
* 2–4 sentences, third person, starting with "The user ..." (or the named person
if the memories are about someone else).
* Cover the topic as a whole — state the current situation, and mention how it
developed only when the change matters.
* Preserve concrete details: names, places, numbers, dates.
* Do NOT invent anything that is not in the memories.
* Do NOT enumerate the memories one by one; synthesize them.
* Output language must match the language of the memories.
Return ONLY the summary text, no preamble, no JSON, no quotes."""
# ================================================================
# System 2 evolve (pipelines/system2_agent.py)
# ================================================================
SYSTEM2_AGENT_PROMPT = """You are a cognitive processing Agent. Your job is to evolve higher-order cognitive structures from a user's raw memory data.
## Memory Architecture
### VDB (Vector Database) — read-only
| Layer | Description |
|-------|-------------|
| L0 BASIC_INFO | User attributes (name, age, occupation) |
| L1 RAW | Original conversation transcripts |
| L2 FACT | Atomic facts extracted from conversations |
| L3 SUMMARY | Session summaries |
| L4 IDENTITY | Identity traits, preferences, habits |
| L5 KNOWLEDGE | Domain knowledge |
### Graph (Knowledge Graph) — you are the sole writer
| Layer | Description |
|-------|-------------|
| L6 SCHEMA | Behavioral patterns — domain-bound, atomic, immutable once created |
## What is an L6 Schema?
A Schema is one cross-event behavioral pattern in the long-term user model and
contains the following three components:
- **Circumstance**: The domain/topic/situation where this pattern is observed. A Schema MUST stay within its circumstance — do NOT generalize across domains. Cross-domain abstraction is handled separately by the system.
- **Pattern**: The user's habitual behavior, thinking style, or action tendency in this circumstance.
- **Insight**: The underlying psychological driver or mental model.
### Content format
Use a single sentence combining all three:
"When [circumstance], the user [pattern] — reflecting [insight]."
For durable emotional, therapy, stress, grief, or trauma-related patterns, do
not erase the specific autobiographical background into a generic personality
label. Prefer an anchored structure that preserves:
"Trigger/background -> recurring emotional response -> observed coping resource."
Only include triggers, responses, and coping resources present in user-owned
evidence. Assistant advice is not an observed user coping resource.
### Rules
- **Atomic**: One pattern per Schema. Two distinct patterns → two Schemas.
- **Immutable**: Once created, content NEVER changes.
- **Evidence only**: New facts that merely support existing wording → call
`add_evidence`. If new evidence materially changes, corrects, or narrows the
current durable state, use `revise_schema` so the old version is retained for
audit but removed from active retrieval.
- **Domain-bound**: Stay within the observed domain. Cross-domain abstraction is handled by a separate system process.
- **Separate facts from patterns**: A single explicit identity, health state,
equipment use, affiliation, project, or preference belongs in L2 state memory,
not L6. L6 requires user-owned evidence representing at least two independent
raw writes or sessions. One reconciled L2 fact may represent multiple writes;
inspect its source_raw_memory_ids rather than counting fact nodes.
- **Subject safety**: Facts sharing a source chunk may describe other people or
examples. Create a user Schema only when the material explicitly attributes
the pattern to the user. Otherwise output no operation for that material.
- **Candidate coverage**: In a source-context review, account for every listed
candidate fact: either use it as evidence in `create_schema`/`add_evidence`,
or emit `skip_fact`. Raw context may confirm its subject or restore omitted
detail, but must not seed unrelated Schemas.
- **Evidence threshold**: Creating L6 requires one or more user-owned evidence
facts representing at least two independent raw writes or sessions.
Assistant advice does not establish a user pattern by itself.
### Good vs Bad examples
✅ "When cooking, the user strictly follows recipe steps and precisely measures ingredients — reflecting a need for external structure to manage uncertainty."
✅ "When gaming, the user always checks walkthroughs to achieve 100% completion — driven by low tolerance for incompleteness."
✅ "After an unexpected mentor loss, mortality reminders can bring grief back suddenly; the user pauses to reflect and takes quiet walks to regain steadiness."
❌ "The user approaches music as a medium for creative synthesis and cultural bridge-building, consistently blending diverse musical traditions..." (multiple patterns merged, too broad)
❌ "The user is passionate about many things and values quality." (no circumstance, too vague)
❌ "The user is meaning-driven and resilient." (erases the durable trigger and observed coping evidence)
## Your Workflow
Input materials (you are only invoked when at least one cluster exists):
- **Cluster Results**: Primary input — facts grouped by semantic similarity
- **Unclustered Facts (supplemental)**: Facts not in any cluster; lower priority than cluster results
For each group of facts (clustered or unclustered):
1. **Search existing Schemas**: If an existing Schema already captures this pattern, call `add_evidence`. Use `revise_schema` only when the old wording would become materially incomplete or misleading after adding the new evidence.
2. **Create new Schema**: Only if no existing Schema covers this pattern. One sentence, three components.
3. **Build relationships**: If two Schemas are thematically related, use `add_edge` (RELATED_TO).
## Principles
- All Graph operations MUST go through tools
- Schema content is IMMUTABLE — never recreate what already exists
- Prefer `add_evidence` over creating duplicates
- Prefer not creating over creating low-quality nodes
- One Schema = one atomic pattern in one domain
- When tagging, prefer reusing tags from the existing tags list"""
# ================================================================
# Fragments assembled in code
# ================================================================
# Display name of this language, used in logs and docs.
LANGUAGE_NAME = "English"
# Appended to every memory-writing prompt ONLY when the caller set the
# language explicitly (per call or MemoryConfig.language). Auto-detected
# requests never see it, so their prompts stay exactly as before.
OUTPUT_LANGUAGE_DIRECTIVE = """
---
# OUTPUT LANGUAGE
Write every natural-language value you output (memory text, summaries, reasons, queries) in English, whatever language the conversation or the existing memories use. This overrides any rule above that says to match the input language. Keep JSON keys, field names, enum values, operation names, IDs, code, file paths, commands, error messages and proper nouns exactly as they are.
"""
# enable_intention=False: the "two kinds of memory" section of
# EXTRACT_SYSTEM_PROMPT is replaced by a single-bucket section. The pattern
# must match this language's EXTRACT_SYSTEM_PROMPT.
NO_INTENTION_SECTION_PATTERN = r"# TWO KINDS OF MEMORY.*?(?=\n# OWNER\n)"
NO_INTENTION_MEMORY_SECTION = (
"# MEMORY\n"
"\n"
"You extract one kind of memory: **memory**.\n"
"\n"
"## memory\n"
"\n"
"Everything durable and worth remembering about the user — and about any other\n"
"named person in the conversation — goes here:\n"
"\n"
"* preferences, opinions, values, attitudes\n"
"* events, actions, experiences\n"
"* habits, behavioral patterns\n"
"* relationships, traits\n"
"* objective facts, decisions, constraints\n"
"* ongoing activities\n"
"* **future plans / upcoming actions** that are worth remembering\n"
" (put them in `memory`; do NOT invent a separate intentions bucket)\n"
"\n"
"Implicit preferences MUST be inferred when strongly suggested.\n"
"\n"
"Do NOT separate \"what kind of person the user is\" from \"what happened\".\n"
"Both go into `memory`.\n"
"\n"
"---\n"
"\n"
)
# (old, new) pairs applied in order after the section swap.
NO_INTENTION_REPLACEMENTS = (
(
"Every memory and intention MUST carry an `owner`",
"Every memory MUST carry an `owner`",
),
)
INTENTION_DISABLED_NOTE = (
"\n\n---\n\n# INTENTION DISABLED\n"
"Intention extraction is disabled. Even if few-shot examples show "
"`intentions`, do **not** emit that key; put future plans in `memory`.\n"
)
# Coding / working extractor: wraps the history and the new messages.
CODING_CONVERSATION_BLOCK = (
"LAST 5 USER TURNS (CONTEXT ONLY; do not extract them again):\n"
"{history}"
"\n\nNEW MESSAGES (extract only from this complete write):\n"
"{new}"
)
CODING_EMPTY_BLOCK = "(none)"
# Name line at the top of a multi-person conversation. Also stored in L1_RAW,
# so pipelines.base parses every language's form back.
USER_NAME_LINE = "The user's name is {name}"
# System 2 single-call mode: output contract appended to SYSTEM2_AGENT_PROMPT.
SYSTEM2_OUTPUT_INSTRUCTIONS = """
## Output Format
Do NOT call tools. Output a JSON array of operations directly, wrapped in a ```json code block.
Available operations:
1. **create_schema** — Create a new L6 behavioral Schema
```{"op": "create_schema", "schema_kind": "behavior_pattern", "content": "concise user-facing pattern", "retrieval_text": "behavior plus relevant situations", "evidence_list": ["fact_node_id_1", "fact_node_id_2"], "tags": ["tag1"]}```
2. **add_evidence** — Add evidence to an existing Schema (content stays immutable)
```{"op": "add_evidence", "node_id": "existing_schema_node_id", "evidence_list": ["new_fact_id_1"]}```
3. **revise_schema** — Create a new version when new evidence materially changes the current state and retire the old version from active retrieval. Do not use it for compatible detail.
```{"op": "revise_schema", "node_id": "old_schema_id", "schema_kind": "explicit_state", "content": "updated current profile", "retrieval_text": "updated retrieval text", "evidence_list": ["new_fact_id_1"], "tags": ["tag1"], "reason": "why the old wording would mislead"}```
4. **add_edge** — Create a relationship between two Schemas
```{"op": "add_edge", "source_id": "schema_id_1", "target_id": "schema_id_2", "reason": "relationship reason"}```
5. **skip_fact** — Explicitly mark a candidate fact as reviewed without creating
a Schema. Use only for facts about another person, transient events, assistant
advice that does not establish a user trait, or facts already fully covered.
```{"op": "skip_fact", "node_id": "candidate_fact_id", "reason": "not a durable user pattern"}```
## Output Contract
1. Output MUST be a JSON array wrapped in a ```json code block.
2. If data is insufficient for reliable conclusions, output `[]`. In a
source-context review, `[]` means every candidate in that review was examined
and none supports a durable Schema.
3. NO text outside the code block.
4. evidence_list IDs must come from the fact node_ids provided in the materials.
5. add_evidence / revise_schema node_id must come from existing Graph node IDs.
6. For a source-context review, every candidate fact must appear in an evidence_list or skip_fact."""
# Appended to SYSTEM2_OUTPUT_INSTRUCTIONS when the schema evidence gate is on.
SYSTEM2_SCHEMA_EVIDENCE_GATE = """
7. L6 represents cross-event behavioral patterns only. create_schema must use user-owned evidence representing at least two independent raw writes or sessions. A single reconciled fact can qualify when source_raw_memory_ids contains multiple independent writes.
8. Do not create L6 for a single explicit identity, health state, equipment use, affiliation, project, or preference. Canonical L2 state projection owns those facts."""
# (old, new) pairs that turn the tool-calling wording of SYSTEM2_AGENT_PROMPT
# into single-call wording. Each `old` must occur in this language's prompt
# (or be harmless if it does not).
SYSTEM2_SINGLE_CALL_REPLACEMENTS = (
("- All Graph operations MUST go through tools\n", ""),
("call `add_evidence`", "use add_evidence operation"),
("use `add_edge` (RELATED_TO)", "use add_edge operation"),
)
# System 2 user message (materials). Keys are fixed; values are format
# strings with the named fields shown.
SYSTEM2_MATERIALS = {
"title": "## Preprocessed Materials\n",
"stats": (
"### Statistics\n- Total VDB facts: {total}\n"
"- Fresh facts (to process): {fresh} (evidenced_excluded={evidenced})\n"
"- Clusters found: {clusters}\n"
"- Facts in clusters: {clustered}, unclustered supplement: {unclustered}\n"
"- Graph Schemas total: {graph_total} (recalled={recalled})"
),
"graph_status_empty": "- Graph status: **EMPTY** (first cognitive processing run)\n",
"clusters_header": "### Cluster Results\n",
"label_holistic": "{n} cross-session user facts; only evidence-backed durable emotional/therapy patterns",
"label_source_context": "{n} candidate facts; raw write is only for subject/detail validation",
"label_topic": "{n} facts, topic",
"raw_label": "Relevant raw excerpt per candidate",
"holistic_note": (
"This is one bounded nightly holistic consolidation pass. Create at most "
"{max_schemas} anchored therapy Schemas. "
"Prefer joining durable trigger/background, recurring emotional response, "
"and observed coping resource or decision constraint across different raw writes/sessions. "
"Before create_schema, union raw_sources for its evidence IDs and require at least two "
"distinct raw source IDs; two facts from one raw write are one event. "
"Output [] when evidence is insufficient.\n"
),
"companion_header": "**Independent-source companion hints (candidates, not conclusions):**",
"unclustered_header": "### Unclustered Facts (supplemental)\n",
"unclustered_hint": "Facts below were not grouped into any cluster above. Review after cluster themes; lower priority than cluster results.\n",
"forward_header": "### Related Graph Nodes (retrieved via cluster centroid search, {count} hits)\n",
"graph_empty": "### Existing Graph Nodes\nThe Graph is currently EMPTY — this is the first cognitive processing run. Do NOT search the Graph (it will return nothing). Proceed directly to create Schemas from the cluster results.\n",
"graph_no_recall": (
"### Existing Graph Nodes\n"
"The Graph has {graph_total} Schemas, "
"but centroid-based vector recall found no matches. Decide based on cluster results whether new Schemas are needed.\n"
),
"reverse_header": "### Graph Reverse References (VDB facts referenced by existing Schemas)\n",
"tags_header": "### Existing Tags (prefer reusing these over creating synonyms)\n",
"closing": (
"Analyze the materials above and output a JSON array of operations. "
"Decide whether to create new Schemas, or add evidence to existing ones. "
"If the data is insufficient for reliable conclusions, output an empty array `[]`. "
"When tagging, prefer reusing tags from the existing tags list above."
),
}