NewRelease 0.12.3 is out: Engine v0.12.3, Dashboard 0.5.2, Helm 0.12.3, the four SDKs, CLI 0.8.2 and MCP 0.12.3Release 0.12.3 is outSee what's new →
Intermediate Agent Behavior

Feedback Loop Memory

⏱ ~25 min to implement 📦 Requires: Dakera v0.11+

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 →
Prerequisites
  • 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:

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

Workflow of how the agent handles feedback. Each user message goes through signal detection, which also classifies its domain. Positive feedback sends an upvote for the lessons that were in the prompt; Dakera raises each one's importance by 0.05 plus 5% of its current value. A correction is kept only if it is persistent and names both the wrong and the right behavior; one-off task instructions and vague corrections are not stored. A kept correction first recalls the lessons in the same domain: a contradicted old lesson drops to importance 0.50, below the recall threshold, and is kept as history. Then store_memory() saves the new LESSON with importance 0.94 and a domain tag.Workflow of how the agent handles feedback. Each user message goes through signal detection, which also classifies its domain. Positive feedback sends an upvote for the lessons that were in the prompt; Dakera raises each one's importance by 0.05 plus 5% of its current value. A correction is kept only if it is persistent and names both the wrong and the right behavior; one-off task instructions and vague corrections are not stored. A kept correction first recalls the lessons in the same domain: a contradicted old lesson drops to importance 0.50, below the recall threshold, and is kept as history. Then store_memory() saves the new LESSON with importance 0.94 and a domain tag.
Corrections become new lessons; an upvote reinforces the lessons you already have.Full size ↗Full size ↗
Sequence across sessions between the user, your agent and Dakera. A correction such as don't use bullet points makes the agent call store_memory with a lesson at importance 0.94, tagged type:lesson and its domain. On every later response the agent recalls lessons with top_k=8 and min_importance=0.85 and injects them as numbered rules in the system prompt before it answers. When the user says exactly like that, the agent sends an upvote for the lessons that were used, which raises their importance by 0.05 plus 5% of the current value.Sequence across sessions between the user, your agent and Dakera. A correction such as don't use bullet points makes the agent call store_memory with a lesson at importance 0.94, tagged type:lesson and its domain. On every later response the agent recalls lessons with top_k=8 and min_importance=0.85 and injects them as numbered rules in the system prompt before it answers. When the user says exactly like that, the agent sends an upvote for the lessons that were used, which raises their importance by 0.05 plus 5% of the current value.
Lessons are recalled before every response in the same domain.Full size ↗Full size ↗

Implementation Steps

Design: Always store what was wrong AND what is right

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.

Try Free →

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:

Before / After Memory State

Before: No Feedback Memory
// 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.
After: Feedback Loop Active
// 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

MethodSDKPurpose in this pattern
store_memory(agent_id, content, importance, memory_type, tags)PythonPersist lesson with very high importance
feedback_memory(memory_id, agent_id, signal="upvote")PythonReinforce the lessons used after positive feedback
update_importance(agent_id, memory_ids, importance)PythonDemote a contradicted lesson to 0.50
recall(agent_id, query, top_k, min_importance, tags)PythonRetrieve lessons for domain before responding
storeMemory(agentId, {content, importance, memory_type, tags})TypeScriptStore lesson from user correction
feedbackMemory(memoryId, agentId, 'upvote')TypeScriptUpvote the lessons used after a positive signal
recall(agentId, query, {top_k, min_importance, tags})TypeScriptRecall domain-specific lessons
client.store_memory(StoreMemoryRequest::new(agent, content).with_tags(...)).await?RustAsync lesson storage with domain tags
client.recall(RecallRequest::new(agent, query).with_top_k(8)).await?RustAsync lesson recall
client.feedback_memory(id, agent, FeedbackSignal::Upvote).await?RustUpvote the lessons used
client.StoreMemory(ctx, agentID, dakera.StoreMemoryRequest{...})GoStore lesson with domain tags
client.Recall(ctx, agentID, dakera.RecallRequest{...})GoRecall lessons for system prompt injection
client.FeedbackMemory(ctx, id, agentID, dakera.FeedbackSignalUpvote)GoUpvote the lessons used

Edge Cases and Gotchas

Warning: Distinguish corrections from task instructions

"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

0.85
min_importance for lesson recall
0.94
Importance for correction lessons
8
Max lessons to inject per response
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}"]
    )
Try it live

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.