Feedback Loop Memory
Every user correction is a free training signal, but most agents throw it away. When a user says "no, don't use bullet points" or "actually I prefer UK English", that preference should persist across every future session. This pattern closes the feedback loop: corrections become lasting memories that prevent repeated mistakes and accumulate into a personalized behavioral profile.
Start Building Free →- Dakera instance running (quickstart)
- SDK installed:
pip install dakera/npm i @dakera-ai/dakera - Correction detection logic — pattern matching on "no", "actually", "don't", "I prefer", "stop doing", explicit reformatting requests
- A domain tagging strategy so corrections are scoped correctly and don't bleed across unrelated domains
The Problem
Without feedback memory, agents are Sisyphean — they learn nothing across sessions. Three failure modes compound over time:
- Repeated style violations: A user corrects "please don't use emoji in technical docs" once, twice, three times. Without persistent storage, the agent reverts to its defaults every session. By the fifth correction, the user has given up and the product has lost their trust.
- Lost domain calibration: A technical writer corrects how the agent structures API documentation. A legal researcher corrects citation format. A marketer corrects brand voice. These are domain-specific lessons that should persist indefinitely, but evaporate with each new session.
- No positive reinforcement: When the agent does something well and the user says "yes, exactly like that — do it this way every time," there's nowhere to store that positive signal. The agent can't consistently replicate what worked.
Architecture
This pattern stores every detected correction as a lesson memory at importance 0.94. Lessons are tagged by domain so they can be recalled selectively. Before generating any response, the agent recalls lessons relevant to the current domain and injects them as behavioral constraints into the system prompt. Negative feedback creates new lesson entries; positive feedback upvotes the lessons that shaped the praised response, which raises their importance (Dakera adds 0.05 plus 5% of the current value, up to 1.0).
Implementation Steps
-
Build a correction detectorScan each user message for correction signals: negation words ("no", "don't", "stop", "never", "please avoid"), reformulation phrases ("actually", "I meant", "I prefer", "instead use"), and explicit instruction phrases ("always do X", "from now on", "every time you"). Classify detected corrections by domain (formatting, tone, language, code style, factual accuracy).
-
Store lessons with very high importance and domain tagsCorrections get importance 0.94, well above ordinary memories (the default is 0.5), so they rank first when recalled. Include both the wrong behavior and the correct behavior in the content: "LESSON: Do NOT use bullet points when writing blog posts. Use flowing prose paragraphs instead." Tag with the domain:
domain:formatting,domain:language,domain:code. -
Detect positive signals and upvote the lessons usedWhen the user says "exactly like that", "perfect", "yes, this is what I wanted", detect the positive signal and send an
upvotefor each lesson that was in the prompt (POST /v1/memories/{id}/feedback,feedback_memory()in Python). Dakera raises each one's importance by 0.05 plus 5% of its current value, up to 1.0, and counts the upvote as an access. If no lesson covered the praised behavior, store it as a new positive lesson at importance 0.91. -
Recall domain-relevant lessons before every responseBefore generating any response, identify the domain of the current task (writing, coding, analysis, etc.) and recall the top 8 lessons for that domain. Use
min_importance=0.85to filter out weak signals. Inject all recalled lessons as a numbered constraint list in the system prompt: "User behavioral rules: 1. Never use bullet points..." -
Periodically consolidate contradicting lessonsOver time, users may give contradictory instructions (prefer bullets for summaries but not for articles). Detect contradictions at storage time by recalling existing lessons in the same domain and comparing. When a new lesson directly contradicts an existing one, lower the old lesson's importance to 0.50 with
update_importance(), below the 0.85 recall threshold. Store both so history is preserved.
A lesson memory needs both sides of the correction to be actionable: "LESSON: Do NOT write bullet point lists for blog content. Always use continuous prose paragraphs with clear topic sentences." A lesson that only says "no bullet points" gives the model no positive direction and leads to inconsistent behavior. The more specific the positive instruction, the more reliable the correction.
Implementation
# Store a lesson from user correction
curl -X POST http://localhost:3000/v1/memory/store \
-H "Authorization: Bearer dk-..." \
-H "Content-Type: application/json" \
-d '{
"agent_id": "writing-assistant-user-1",
"content": "LESSON: Do NOT use bullet point lists when writing blog posts or articles. Always use flowing prose paragraphs with clear topic sentences instead.",
"importance": 0.94,
"memory_type": "semantic",
"tags": ["type:lesson", "domain:formatting", "feedback:negative"],
"metadata": {
"correction_type": "formatting",
"wrong_behavior": "bullet point lists",
"correct_behavior": "prose paragraphs",
"trigger": "user_correction"
}
}'
# Store a positive lesson (user loved a behavior no lesson covered yet)
curl -X POST http://localhost:3000/v1/memory/store \
-H "Authorization: Bearer dk-..." \
-H "Content-Type: application/json" \
-d '{
"agent_id": "writing-assistant-user-1",
"content": "LESSON: User prefers Oxford comma usage. Always include Oxford comma in lists. User explicitly confirmed: '\''exactly like this every time'\''.",
"importance": 0.91,
"memory_type": "semantic",
"tags": ["type:lesson", "domain:language", "feedback:positive"],
"metadata": {"correction_type": "punctuation", "correct_behavior": "oxford_comma"}
}'
# Upvote a lesson that was in the prompt when the user said "exactly like that"
# (importance rises by 0.05 plus 5% of its current value, at most 1.0)
curl -X POST http://localhost:3000/v1/memories/mem_abc123/feedback \
-H "Authorization: Bearer dk-..." \
-H "Content-Type: application/json" \
-d '{"agent_id": "writing-assistant-user-1", "signal": "upvote"}'
# Recall domain lessons before responding
curl -X POST http://localhost:3000/v1/memory/recall \
-H "Authorization: Bearer dk-..." \
-H "Content-Type: application/json" \
-d '{
"agent_id": "writing-assistant-user-1",
"query": "lessons corrections formatting writing style rules",
"top_k": 8,
"min_importance": 0.85,
"tags": ["type:lesson"]
}'import re
from dakera import DakeraClient
from typing import Optional
client = DakeraClient(base_url="http://localhost:3000", api_key="dk-...")
CORRECTION_PATTERNS = [
r"\bdon'?t\b.*\b(use|write|include|add|put)\b",
r"\b(never|stop|no more|avoid)\b.*\b(using|writing|adding|including)\b",
r"\b(please|always)\b.*\binstead\b",
r"\bactually,?\b",
r"\bi prefer\b",
r"\bfrom now on\b",
r"\bevery time\b",
r"\balways\b.*\b(use|do|write|format)\b",
]
POSITIVE_PATTERNS = [
r"\bexactly (like|right|that|what i)\b",
r"\b(perfect|yes,? that'?s?|love this|great job)\b",
r"\bdo it this way\b",
r"\bkeep (doing|it like)\b",
]
DOMAIN_KEYWORDS = {
"formatting": ["bullet", "list", "header", "section", "paragraph", "bold", "italic", "indent", "table"],
"language": ["english", "british", "american", "spelling", "grammar", "comma", "apostrophe", "punctuation"],
"tone": ["formal", "casual", "technical", "friendly", "professional", "warm", "direct", "concise"],
"code": ["typescript", "python", "javascript", "function", "async", "class", "variable", "comment"],
"structure": ["introduction", "conclusion", "outline", "summary", "example", "reference", "citation"],
}
def detect_correction(message: str) -> Optional[dict]:
"""Returns correction info if the message contains a correction signal."""
msg_lower = message.lower()
for pattern in CORRECTION_PATTERNS:
if re.search(pattern, msg_lower):
domain = classify_domain(msg_lower)
return {"type": "correction", "domain": domain, "message": message}
return None
def detect_positive_feedback(message: str) -> bool:
msg_lower = message.lower()
return any(re.search(p, msg_lower) for p in POSITIVE_PATTERNS)
def classify_domain(message: str) -> str:
message_lower = message.lower()
scores = {domain: 0 for domain in DOMAIN_KEYWORDS}
for domain, keywords in DOMAIN_KEYWORDS.items():
scores[domain] = sum(1 for kw in keywords if kw in message_lower)
return max(scores, key=scores.get) if any(scores.values()) else "general"
def store_lesson(agent_id: str, wrong_behavior: str, correct_behavior: str, domain: str) -> None:
"""Store a user correction as a persistent behavioral lesson."""
content = (
f"LESSON: Do NOT {wrong_behavior} when writing. "
f"Always {correct_behavior} instead."
)
client.store_memory(
agent_id=agent_id,
content=content,
importance=0.94, # high, so lessons rank above ordinary memories
memory_type="semantic",
tags=["type:lesson", f"domain:{domain}", "feedback:negative"],
)
def store_positive_lesson(agent_id: str, behavior: str, domain: str) -> None:
"""Store a preferred behavior confirmed by user positive feedback."""
client.store_memory(
agent_id=agent_id,
content=f"LESSON: User confirmed they love this: {behavior}. Always do this.",
importance=0.91,
memory_type="semantic",
tags=["type:lesson", f"domain:{domain}", "feedback:positive"],
)
def upvote_lessons(agent_id: str, memory_ids: list[str]) -> None:
"""Reinforce the lessons that shaped a response the user praised."""
for memory_id in memory_ids:
# Dakera raises importance by 0.05 plus 5% of its current value (at most 1.0)
client.feedback_memory(memory_id=memory_id, agent_id=agent_id, signal="upvote")
def get_lessons_for_domain(agent_id: str, domain: str) -> list:
"""Recall the lessons relevant to the current domain."""
response = client.recall(
agent_id=agent_id,
query=f"lessons corrections rules for {domain} writing style behavior",
top_k=8,
min_importance=0.85,
tags=["type:lesson"],
)
return [m for m in response.memories if m.content.startswith("LESSON:")]
def build_behavioral_constraints(lessons: list) -> str:
"""Build a system prompt section from recalled lessons."""
if not lessons:
return ""
constraint_block = "User behavioral rules (MUST follow):\n"
for i, lesson in enumerate(lessons, 1):
# Strip the LESSON: prefix for cleaner prompt injection
rule = lesson.content.replace("LESSON: ", "").strip()
constraint_block += f"{i}. {rule}\n"
return constraint_block
# --- Full writing assistant workflow ---
last_lesson_ids: list[str] = [] # lessons used in the previous response
def process_user_message(agent_id: str, message: str, task_domain: str = "general") -> str:
global last_lesson_ids
# 1. Check for feedback signals
correction = detect_correction(message)
if correction:
# Parse wrong/correct from message (simplified -- use LLM extraction in production)
store_lesson(agent_id, "use bullet points", "use prose paragraphs", correction["domain"])
return "Got it, I'll use prose paragraphs from now on."
if detect_positive_feedback(message):
if last_lesson_ids:
upvote_lessons(agent_id, last_lesson_ids)
else:
store_positive_lesson(agent_id, "the style of the last response", task_domain)
return "Great, I'll keep doing it this way!"
# 2. Build constraints from recalled lessons
lessons = get_lessons_for_domain(agent_id, task_domain)
last_lesson_ids = [m.id for m in lessons]
constraints = build_behavioral_constraints(lessons)
# 3. Inject into system prompt for your LLM call
# system_prompt = f"{BASE_SYSTEM_PROMPT}\n\n{constraints}"
# response = llm.complete(system=system_prompt, user=message)
return f"[Constraints active]\n{constraints}"
# --- Example ---
agent = "writing-assistant-user-1"
store_lesson(agent, "use bullet points in blog articles", "use flowing prose paragraphs with clear topic sentences", "formatting")
store_lesson(agent, "use American English spellings", "use British English (colour, realise, etc.)", "language")
print(build_behavioral_constraints(get_lessons_for_domain(agent, "formatting")))import { DakeraClient } from '@dakera-ai/dakera';
const client = new DakeraClient({ baseUrl: 'http://localhost:3000', apiKey: 'dk-...' });
const CORRECTION_PATTERNS = [
/\bdon'?t\b.*\b(use|write|include|add)\b/i,
/\b(never|stop|avoid|no more)\b.*\b(using|writing|adding)\b/i,
/\b(always|please)\b.*\binstead\b/i,
/\bactually,?\b/i,
/\bi prefer\b/i,
/\bfrom now on\b/i,
];
const POSITIVE_PATTERNS = [
/\bexactly (like|right|that)\b/i,
/\b(perfect|love this|great job)\b/i,
/\bdo it this way\b/i,
];
function detectCorrection(message: string): boolean {
return CORRECTION_PATTERNS.some(p => p.test(message));
}
function detectPositiveFeedback(message: string): boolean {
return POSITIVE_PATTERNS.some(p => p.test(message));
}
async function storeLesson(
agentId: string,
wrongBehavior: string,
correctBehavior: string,
domain: string
): Promise<void> {
await client.storeMemory(agentId, {
content: `LESSON: Do NOT ${wrongBehavior}. Always ${correctBehavior} instead.`,
importance: 0.94,
memory_type: 'semantic',
tags: ['type:lesson', `domain:${domain}`, 'feedback:negative'],
});
}
async function storePositiveLesson(agentId: string, behavior: string, domain: string): Promise<void> {
await client.storeMemory(agentId, {
content: `LESSON: User confirmed they prefer: ${behavior}. Always do this.`,
importance: 0.91,
memory_type: 'semantic',
tags: ['type:lesson', `domain:${domain}`, 'feedback:positive'],
});
}
// Upvote raises importance by 0.05 plus 5% of the current value (at most 1.0)
async function upvoteLessons(agentId: string, memoryIds: string[]): Promise<void> {
for (const id of memoryIds) {
await client.feedbackMemory(id, agentId, 'upvote');
}
}
async function getLessonsForDomain(agentId: string, domain: string) {
const { memories } = await client.recall(
agentId,
`lessons corrections rules for ${domain} writing style`,
{ top_k: 8, min_importance: 0.85, tags: ['type:lesson'] }
);
return memories.filter(m => m.content.startsWith('LESSON:'));
}
function buildConstraints(lessons: { content: string }[]): string {
if (lessons.length === 0) return '';
return 'User behavioral rules (MUST follow):\n' +
lessons.map((l, i) => `${i + 1}. ${l.content.replace('LESSON: ', '').trim()}`).join('\n');
}
// Usage
await storeLesson('writing-assistant-user-1', 'use bullet points in blog content', 'use prose paragraphs', 'formatting');
const lessons = await getLessonsForDomain('writing-assistant-user-1', 'formatting');
console.log(buildConstraints(lessons));
// Later, the user says "exactly like that": reinforce the lessons that were in the prompt
await upvoteLessons('writing-assistant-user-1', lessons.map(l => l.id));use dakera_client::{DakeraClient, FeedbackSignal, MemoryType, RecallRequest, StoreMemoryRequest};
let client = DakeraClient::builder("http://localhost:3000")
.api_key("dk-...")
.build()?;
// Store a lesson from user correction
client.store_memory(
StoreMemoryRequest::new(
"writing-assistant-user-1",
"LESSON: Do NOT use bullet point lists for blog posts. Always use flowing prose paragraphs with clear topic sentences instead.",
)
.with_type(MemoryType::Semantic)
.with_importance(0.94)
.with_tags(vec!["type:lesson".into(), "domain:formatting".into(), "feedback:negative".into()]),
).await?;
// Recall lessons for the domain before responding
let lessons = client.recall(
RecallRequest::new("writing-assistant-user-1", "lessons corrections rules for formatting writing style")
.with_top_k(8)
.with_min_importance(0.85)
.with_tags(vec!["type:lesson".into()]),
).await?;
// Build the constraint block for the system prompt
let rules: Vec<_> = lessons.memories.iter()
.filter(|m| m.content.starts_with("LESSON:"))
.collect();
let constraints: String = rules.iter()
.enumerate()
.map(|(i, m)| format!("{}. {}", i + 1, m.content.replace("LESSON: ", "")))
.collect::<Vec<_>>()
.join("\n");
println!("User behavioral rules:\n{}", constraints);
// Positive feedback: upvote the lessons that shaped the praised response
for m in &rules {
client.feedback_memory(&m.id, "writing-assistant-user-1", FeedbackSignal::Upvote).await?;
}client := dakera.NewClientWithOptions(dakera.ClientOptions{
BaseURL: "http://localhost:3000",
APIKey: "dk-...",
})
ctx := context.Background()
// Store a lesson from correction
importance := float32(0.94)
_, err := client.StoreMemory(ctx, "writing-assistant-user-1", dakera.StoreMemoryRequest{
Content: "LESSON: Do NOT use bullet points for blog articles. Always use prose paragraphs with clear topic sentences instead.",
MemoryType: "semantic",
Importance: &importance,
Tags: []string{"type:lesson", "domain:formatting", "feedback:negative"},
})
// Recall lessons before responding
minImportance := float32(0.85)
lessons, err := client.Recall(ctx, "writing-assistant-user-1", dakera.RecallRequest{
Query: "lessons corrections rules formatting writing style",
TopK: 8,
MinImportance: &minImportance,
})
// Build constraint block
var constraints []string
var usedIDs []string
for _, mem := range lessons.Memories {
if strings.HasPrefix(mem.Content, "LESSON:") {
rule := strings.TrimPrefix(mem.Content, "LESSON: ")
constraints = append(constraints, fmt.Sprintf("%d. %s", len(constraints)+1, rule))
usedIDs = append(usedIDs, mem.ID)
}
}
constraintBlock := "User behavioral rules:\n" + strings.Join(constraints, "\n")
fmt.Println(constraintBlock)
// Positive feedback: upvote the lessons that were in the prompt
for _, id := range usedIDs {
_, err = client.FeedbackMemory(ctx, id, "writing-assistant-user-1", dakera.FeedbackSignalUpvote)
}Turn every correction into a lasting improvement
Dakera memory closes the feedback loop: your agent learns from every correction instead of repeating the same mistake.
Real-World Scenario: Writing Assistant Learning User Style
A writing assistant serves a technical author who writes developer documentation. Over 30 sessions, the agent accumulates a personalized style profile through feedback loop memory:
- Session 1: Agent uses emoji in a technical article. User corrects: "Please no emoji in technical content." Lesson stored: importance 0.94, domain: formatting.
- Session 3: Agent uses American spellings. User corrects: "I write British English — 'colour', 'realise'." Lesson stored: importance 0.94, domain: language.
- Session 7: Agent writes code examples in Python. User: "I prefer TypeScript examples for this audience." Lesson stored, domain: code.
- Session 12: Agent writes an intro paragraph in passive voice. User says "exactly like that, passive voice works here." No lesson covered it, so the agent stores a positive lesson at importance 0.91.
- Session 30: Agent has 11 active lessons spanning formatting, language, code, and structure. Before each response the agent recalls up to 8 of them for the current domain and puts them in the prompt, so the user no longer repeats corrections.
Before / After Memory State
// Session 15:
User: "Please, for the 6th
time, don't use bullet
points in my articles!"
// Agent has no record of
// previous 5 corrections.
// Defaults to bullet points
// because model training
// prefers them.
// User considering abandoning
// the product.
// Memory store:
{
"content": "LESSON: Do NOT use bullet points. Always use prose paragraphs.",
"importance": 0.94,
"tags": ["type:lesson", "domain:formatting"]
}
// Injected into every prompt:
// "1. Never use bullet points.
// 2. Always UK English.
// 3. Prefer TypeScript code."
// Session 15: the rule is in
// the prompt before the agent
// writes a word.
SDK Method Reference
| Method | SDK | Purpose in this pattern |
|---|---|---|
store_memory(agent_id, content, importance, memory_type, tags) | Python | Persist lesson with very high importance |
feedback_memory(memory_id, agent_id, signal="upvote") | Python | Reinforce the lessons used after positive feedback |
update_importance(agent_id, memory_ids, importance) | Python | Demote a contradicted lesson to 0.50 |
recall(agent_id, query, top_k, min_importance, tags) | Python | Retrieve lessons for domain before responding |
storeMemory(agentId, {content, importance, memory_type, tags}) | TypeScript | Store lesson from user correction |
feedbackMemory(memoryId, agentId, 'upvote') | TypeScript | Upvote the lessons used after a positive signal |
recall(agentId, query, {top_k, min_importance, tags}) | TypeScript | Recall domain-specific lessons |
client.store_memory(StoreMemoryRequest::new(agent, content).with_tags(...)).await? | Rust | Async lesson storage with domain tags |
client.recall(RecallRequest::new(agent, query).with_top_k(8)).await? | Rust | Async lesson recall |
client.feedback_memory(id, agent, FeedbackSignal::Upvote).await? | Rust | Upvote the lessons used |
client.StoreMemory(ctx, agentID, dakera.StoreMemoryRequest{...}) | Go | Store lesson with domain tags |
client.Recall(ctx, agentID, dakera.RecallRequest{...}) | Go | Recall lessons for system prompt injection |
client.FeedbackMemory(ctx, id, agentID, dakera.FeedbackSignalUpvote) | Go | Upvote the lessons used |
Edge Cases and Gotchas
- Lesson contradictions: A user asks for concise responses in one session and detailed explanations in another — for different tasks. Domain-scoping helps but isn't enough. Include context in lesson content: "LESSON: When writing API documentation, be concise. When writing tutorials, include detailed step-by-step examples." Specificity prevents false conflicts.
- Lesson accumulation saturation: After 50+ lessons, recall starts returning irrelevant content. Consolidate lessons quarterly: use the LLM to merge related lessons, remove superseded ones, and re-store consolidated rules. Use
batch_forget()with a filter (for example thetype:lessontag andmax_importance=0.5, which catches demoted lessons) to clear old lessons after consolidation. - Over-constraining the model: A list of 15 behavioral rules injected as constraints can reduce response quality by limiting the model's flexibility. Cap lesson injection at 8 rules per response (the
top_k=8above). For contradicting rules, always use the most recently confirmed one. - Ambiguous corrections: "No, that's not what I meant" is a correction signal but doesn't specify what was wrong. Don't store vague corrections as lessons. Only store lessons where you can clearly identify both the wrong behavior and the desired correct behavior — ideally extracted by asking the LLM to interpret the correction.
- Multi-user shared agents: If multiple users share an agent instance (e.g., a team assistant), lessons from one user will affect others. Always scope the agent_id to the individual user:
"writing-assistant-{user_id}". Never share lesson memory across users. - Lessons decay like other memories: Dakera lowers the importance of memories that are not used (default half-life 168 hours; semantic memories decay at half that speed), and each recall restarts the clock. Lessons for a domain the user has not touched in weeks can fall below
min_importance=0.85. If lessons must never fade, set their memory type's decay toflatin the namespace's memory policy (PUT /v1/namespaces/{ns}/memory_policy).
"Write this in bullet points" (task instruction for this response) is fundamentally different from "I always prefer bullet points" (persistent preference). Don't store task-level instructions as lessons — they'll incorrectly constrain future responses in different contexts. Only store behavioral rules the user explicitly intends to be persistent ("from now on", "always", "every time").
Performance Considerations
Advanced Configuration: Lesson versioning and contradiction detection
Automatically detect when a new lesson contradicts an existing one and handle the conflict gracefully:
def store_lesson_with_dedup(agent_id: str, lesson: str, domain: str) -> None:
# 1. Recall existing lessons in same domain
existing = client.recall(
agent_id=agent_id,
query=f"lessons {domain} writing rules",
top_k=5,
min_importance=0.85,
tags=["type:lesson"]
)
# 2. Ask LLM to detect contradiction (is_contradicting is your own check)
for mem in existing.memories:
if is_contradicting(lesson, mem.content):
# Supersede the old lesson: below the 0.85 recall threshold, kept as history
client.update_importance(
agent_id=agent_id,
memory_ids=[mem.id],
importance=0.50
)
# 3. Store new lesson as definitive
client.store_memory(
agent_id=agent_id,
content=f"LESSON: {lesson}",
importance=0.94,
memory_type="semantic",
tags=["type:lesson", f"domain:{domain}"]
)
Run this pattern against a real Dakera.
The Playground executes live API calls in your browser. No account, no install. Then run it on your own server in five minutes.