Intermediate Agent Behavior

Goal Tracking

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

Users set goals across multiple sessions, but agents without persistent state treat every conversation as the first one. A user who said "I want to launch my product by Q3" three sessions ago needs a proactive agent that remembers that commitment, tracks progress, and surfaces it when relevant — not one that asks the same discovery questions every time.

Start Building Free →
Prerequisites
  • Dakera instance running (quickstart)
  • SDK installed: pip install dakera / npm i @dakera-ai/dakera
  • An agent with multi-session interaction capability
  • Goal detection logic — pattern matching on phrases like "I want to", "my goal is", "I'm trying to", "by [date]"

The Problem

Long-running agents face a fundamental failure mode: they are reactive, not proactive. Without goal persistence:

  • Goals evaporate between sessions: "I want to hit 10,000 monthly users by Q3" is stated once and never referenced again — because the agent's memory resets to zero each session.
  • No milestone tracking: Sub-goals and checkpoints are never linked to the parent objective, so the agent can't tell the user "you've completed 3 of 5 milestones toward your Q3 launch."
  • Conflicting objectives: A user shifts strategy mid-quarter, but both the old and new goal live in the conversation without any conflict detection or resolution.
  • Missed deadline prompts: A goal with a hard deadline approaches with no proactive reminder — the agent only knows about it if the user brings it up again.

Architecture

This pattern stores goals as high-importance semantic memories with structured metadata including status, priority, deadline, and a stable goal_id. Sub-goals link to parent goals via a parent_goal_id field. At session start, the agent recalls all active goals (including any nearing their deadlines) and constructs a proactive context note. Status changes are stored as new memories with the same goal_id, creating an audit trail.

GOAL HIERARCHY TREE Q3 PRODUCT LAUNCH status: active | priority: critical deadline: 2024-09-30 | progress: 40% MVP Feature Set status: completed completed: 2024-07-15 Beta Testing status: active | 60% done deadline: 2024-08-31 Marketing Launch status: pending starts: 2024-09-01 User interviews ✓ Performance tests

Goal hierarchy tree: the root objective links to sub-goals with independent status tracking. Completed milestones are preserved as memory for context; active sub-goals surface at each session start.

PROGRESS TRACKING TIMELINE Jul 1 Goal created Jul 15 MVP done Aug 10 Beta 60% TODAY Aug 31 Beta deadline Sep 30 LAUNCH 0% 40% complete 100%

Progress tracking timeline: goal creation, milestone completions, and the current date are tracked. The agent uses this to compute days remaining and suggest daily actions.

Implementation Steps

  • Detect goal statements in user messages
    Look for goal-signaling phrases: "I want to", "my goal is", "I'm trying to", "I need to finish", "by [month/date/quarter]", "this year I plan to". When detected, extract the objective, any mentioned deadline, and the implied priority (urgent/high/medium). Assign a stable goal_id (slugified from the objective).
  • Store goals with high importance and rich metadata
    Goals get importance 0.90–0.95 — they must always surface during recall. Include status, priority, deadline, goal_id, and progress_pct in metadata. Use memory_type="working" for active goals so they're retrieved first. Tag with "type:goal" and "status:active" for precise filtering.
  • Recall active goals at every session start
    Open each session with a recall() call: query "active goals objectives priorities deadlines", top_k=15, min_importance=0.7. Filter results to status=active. Compute days remaining for any goal with a deadline. Surface any goal with fewer than 14 days remaining as urgent context in the system prompt.
  • Store sub-goals linking to parent via goal_id
    When a user mentions a sub-task related to an existing goal, store it with parent_goal_id in metadata. Sub-goals get importance 0.80–0.90. When recalling the parent goal, also query for sub-goals using the goal_id as search context to reconstruct the full hierarchy.
  • Update goal status without deleting history
    When a goal is completed or abandoned, store a new memory with the updated status rather than deleting the original. Set memory_type="semantic" and reduce importance to 0.70 for completed goals so they don't dominate active-goal recall but remain searchable for context. This preserves the audit trail of what was accomplished.
Tip: Use goal_id for precise updates

Assign a stable goal_id (e.g., "q3-product-launch") when the goal is created. Include it in every subsequent memory for that goal. When recalling, filter memories by goal_id to reconstruct the full history of a single goal across sessions — creation, milestones, status changes, and completion.

Implementation

# Create a new goal
curl -X POST http://localhost:3300/v1/memory/store \
  -H "Authorization: Bearer dk-..." \
  -H "Content-Type: application/json" \
  -d '{
    "agent_id": "productivity-agent-user-1",
    "content": "GOAL: Launch SaaS product to 1,000 paying customers by Q3 2024 (September 30). Currently pre-launch. Priority: critical.",
    "importance": 0.93,
    "memory_type": "working",
    "tags": ["type:goal", "status:active", "priority:critical", "goal:q3-launch"],
    "metadata": {
      "goal_id": "q3-product-launch",
      "status": "active",
      "priority": "critical",
      "deadline": "2024-09-30",
      "progress_pct": 0,
      "target_metric": "1000 paying customers"
    }
  }'

# Store a sub-goal
curl -X POST http://localhost:3300/v1/memory/store \
  -H "Authorization: Bearer dk-..." \
  -H "Content-Type: application/json" \
  -d '{
    "agent_id": "productivity-agent-user-1",
    "content": "SUB-GOAL: Complete beta testing with 50 users by August 31. Part of Q3 launch goal.",
    "importance": 0.87,
    "memory_type": "working",
    "tags": ["type:sub-goal", "status:active", "goal:q3-launch"],
    "metadata": {
      "goal_id": "beta-testing",
      "parent_goal_id": "q3-product-launch",
      "status": "active",
      "deadline": "2024-08-31",
      "progress_pct": 60
    }
  }'

# Recall all active goals (session start)
curl "http://localhost:3300/v1/memory/recall?agent_id=productivity-agent-user-1&query=active+goals+objectives+priorities+deadlines&top_k=15&min_importance=0.7" \
  -H "Authorization: Bearer dk-..."

# Mark goal completed (store new memory, don't delete old)
curl -X POST http://localhost:3300/v1/memory/store \
  -H "Authorization: Bearer dk-..." \
  -H "Content-Type: application/json" \
  -d '{
    "agent_id": "productivity-agent-user-1",
    "content": "GOAL COMPLETED: Launched SaaS product. Reached 1,240 paying customers by September 28, 2024 — 2 days early.",
    "importance": 0.85,
    "memory_type": "semantic",
    "tags": ["type:goal", "status:completed", "goal:q3-launch"],
    "metadata": {"goal_id": "q3-product-launch", "status": "completed", "completed_at": "2024-09-28", "final_metric": "1240 customers"}
  }'
from dakera import DakeraClient
from datetime import datetime, date
from typing import Optional
import re

client = DakeraClient(base_url="http://localhost:3300", api_key="dk-...")

def slugify(text: str) -> str:
    return re.sub(r'[^a-z0-9]+', '-', text.lower()).strip('-')[:40]

def create_goal(
    agent_id: str,
    objective: str,
    priority: str = "medium",
    deadline: Optional[str] = None,  # ISO date string "YYYY-MM-DD"
    parent_goal_id: Optional[str] = None
) -> str:
    """Store a new goal and return its goal_id."""
    goal_id = slugify(objective)
    is_sub_goal = parent_goal_id is not None
    prefix = "SUB-GOAL" if is_sub_goal else "GOAL"

    deadline_note = f" Deadline: {deadline}." if deadline else ""
    parent_note = f" Part of goal: {parent_goal_id}." if is_sub_goal else ""

    client.store_memory(
        agent_id=agent_id,
        content=f"{prefix}: {objective}.{deadline_note}{parent_note}",
        importance=0.87 if is_sub_goal else 0.93,
        memory_type="working",
        tags=["type:goal", "status:active", f"priority:{priority}", f"goal:{goal_id}"],
        metadata={
            "goal_id": goal_id,
            "parent_goal_id": parent_goal_id,
            "status": "active",
            "priority": priority,
            "deadline": deadline,
            "progress_pct": 0,
            "created_at": datetime.utcnow().isoformat()
        }
    )
    return goal_id

def update_goal_progress(agent_id: str, goal_id: str, progress_pct: int, milestone_note: str) -> None:
    """Store a progress update for an existing goal."""
    client.store_memory(
        agent_id=agent_id,
        content=f"PROGRESS UPDATE [{goal_id}]: {progress_pct}% complete. {milestone_note}",
        importance=0.80,
        memory_type="episodic",
        tags=["type:goal-progress", f"goal:{goal_id}"],
        metadata={
            "goal_id": goal_id,
            "progress_pct": progress_pct,
            "milestone": milestone_note,
            "updated_at": datetime.utcnow().isoformat()
        }
    )

def complete_goal(agent_id: str, goal_id: str, outcome: str) -> None:
    """Mark a goal as completed (store new memory — do NOT delete the original)."""
    client.store_memory(
        agent_id=agent_id,
        content=f"GOAL COMPLETED [{goal_id}]: {outcome}",
        importance=0.82,  # Lower than active -- completed goals should not dominate recall
        memory_type="semantic",
        tags=["type:goal", "status:completed", f"goal:{goal_id}"],
        metadata={
            "goal_id": goal_id,
            "status": "completed",
            "completed_at": datetime.utcnow().isoformat(),
            "outcome": outcome
        }
    )

def abandon_goal(agent_id: str, goal_id: str, reason: str) -> None:
    """Mark a goal as abandoned."""
    client.store_memory(
        agent_id=agent_id,
        content=f"GOAL ABANDONED [{goal_id}]: {reason}",
        importance=0.65,
        memory_type="semantic",
        tags=["type:goal", "status:abandoned", f"goal:{goal_id}"],
        metadata={"goal_id": goal_id, "status": "abandoned", "reason": reason}
    )

def get_active_goals(agent_id: str) -> list:
    """Recall all active goals with urgency annotations."""
    results = client.recall(
        agent_id=agent_id,
        query="active goals objectives priorities deadlines current tasks",
        top_k=15,
        min_importance=0.70
    )

    goals = []
    today = date.today()

    for mem in results.get("memories", []):
        meta = mem.get("metadata", {})
        if meta.get("status") != "active":
            continue

        goal = {
            "goal_id": meta.get("goal_id"),
            "content": mem["content"],
            "priority": meta.get("priority", "medium"),
            "progress_pct": meta.get("progress_pct", 0),
            "days_remaining": None,
            "urgent": False
        }

        if meta.get("deadline"):
            try:
                deadline = date.fromisoformat(meta["deadline"])
                days_remaining = (deadline - today).days
                goal["days_remaining"] = days_remaining
                goal["urgent"] = days_remaining <= 14
            except ValueError:
                pass

        goals.append(goal)

    # Sort: critical + urgent first
    return sorted(goals, key=lambda g: (
        0 if g["priority"] == "critical" else 1 if g["priority"] == "high" else 2,
        0 if g["urgent"] else 1
    ))

def build_goal_context_prompt(agent_id: str) -> str:
    """Generate system prompt context from active goals."""
    goals = get_active_goals(agent_id)
    if not goals:
        return "No active goals recorded for this user."

    lines = ["Active goals:"]
    for g in goals:
        urgency = " [URGENT]" if g["urgent"] else ""
        days = f" ({g['days_remaining']} days remaining)" if g["days_remaining"] is not None else ""
        progress = f" — {g['progress_pct']}% complete"
        lines.append(f"  - [{g['priority'].upper()}]{urgency} {g['goal_id']}{progress}{days}")

    return "
".join(lines)

# --- Example usage ---
agent = "productivity-agent-user-1"
goal_id = create_goal(agent, "Launch SaaS product to 1000 paying customers", "critical", "2024-09-30")
sub_goal = create_goal(agent, "Complete beta testing with 50 users", "high", "2024-08-31", goal_id)
update_goal_progress(agent, goal_id, 40, "MVP feature set complete, beta testing at 60%")
print(build_goal_context_prompt(agent))
import { DakeraClient } from '@dakera-ai/dakera';

const client = new DakeraClient({ baseUrl: 'http://localhost:3300', apiKey: 'dk-...' });

type GoalStatus = 'active' | 'completed' | 'abandoned' | 'paused';
type Priority = 'critical' | 'high' | 'medium' | 'low';

interface GoalOptions {
  priority?: Priority;
  deadline?: string;  // ISO date "YYYY-MM-DD"
  parentGoalId?: string;
}

function slugify(text: string): string {
  return text.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '').slice(0, 40);
}

async function createGoal(agentId: string, objective: string, opts: GoalOptions = {}): Promise<string> {
  const goalId = slugify(objective);
  const isSubGoal = !!opts.parentGoalId;
  const prefix = isSubGoal ? 'SUB-GOAL' : 'GOAL';
  const deadlineNote = opts.deadline ? ` Deadline: ${opts.deadline}.` : '';
  const parentNote = opts.parentGoalId ? ` Part of goal: ${opts.parentGoalId}.` : '';

  await client.storeMemory(agentId, {
    content: `${prefix}: ${objective}.${deadlineNote}${parentNote}`,
    importance: isSubGoal ? 0.87 : 0.93,
    memoryType: 'working',
    tags: ['type:goal', 'status:active', `priority:${opts.priority ?? 'medium'}`, `goal:${goalId}`],
  });

  return goalId;
}

async function updateGoalProgress(agentId: string, goalId: string, progressPct: number, milestoneNote: string): Promise<void> {
  await client.storeMemory(agentId, {
    content: `PROGRESS UPDATE [${goalId}]: ${progressPct}% complete. ${milestoneNote}`,
    importance: 0.80,
    memoryType: 'episodic',
    tags: ['type:goal-progress', `goal:${goalId}`],
  });
}

async function completeGoal(agentId: string, goalId: string, outcome: string): Promise<void> {
  await client.storeMemory(agentId, {
    content: `GOAL COMPLETED [${goalId}]: ${outcome}`,
    importance: 0.82,
    memoryType: 'semantic',
    tags: ['type:goal', 'status:completed', `goal:${goalId}`],
  });
}

interface ActiveGoal {
  goalId: string;
  priority: Priority;
  progressPct: number;
  daysRemaining?: number;
  urgent: boolean;
}

async function getActiveGoals(agentId: string): Promise<ActiveGoal[]> {
  const result = await client.recall(
    agentId,
    'active goals objectives priorities deadlines current tasks',
    { top_k: 15, min_importance: 0.7 }
  );

  const today = new Date();
  return result.memories
    .filter(m => m.metadata?.status === 'active')
    .map(m => {
      const meta = m.metadata ?? {};
      const deadline = meta.deadline as string | undefined;
      const daysRemaining = deadline
        ? Math.ceil((new Date(deadline).getTime() - today.getTime()) / 86_400_000)
        : undefined;
      return {
        goalId: meta.goal_id as string,
        priority: (meta.priority as Priority) ?? 'medium',
        progressPct: (meta.progress_pct as number) ?? 0,
        daysRemaining,
        urgent: daysRemaining !== undefined && daysRemaining <= 14,
      };
    })
    .sort((a, b) => {
      const priorityOrder: Record<Priority, number> = { critical: 0, high: 1, medium: 2, low: 3 };
      return priorityOrder[a.priority] - priorityOrder[b.priority];
    });
}
use dakera_rs::{Client, StoreMemoryRequest, RecallRequest};
use std::collections::HashMap;

let client = Client::new("http://localhost:3300", "dk-...");

// Create a goal
client.store_memory("productivity-agent-user-1", StoreMemoryRequest {
    content: "GOAL: Launch SaaS product to 1000 paying customers. Deadline: 2024-09-30. Priority: critical.".into(),
    importance: Some(0.93),
    memory_type: "working".into(),
    tags: vec![
        "type:goal".into(),
        "status:active".into(),
        "priority:critical".into(),
        "goal:q3-product-launch".into(),
    ],
    ..Default::default()
}).await?;

// Recall active goals at session start
let goals = client.recall("productivity-agent-user-1", RecallRequest {
    query: "active goals objectives priorities deadlines".into(),
    top_k: Some(15),
    min_importance: Some(0.70),
    ..Default::default()
}).await?;

// Filter to active goals and compute urgency
for goal in &goals.memories {
    if let Some(meta) = &goal.metadata {
        if meta.get("status").and_then(|v| v.as_str()) == Some("active") {
            let goal_id = meta.get("goal_id").and_then(|v| v.as_str()).unwrap_or("unknown");
            println!("Active goal: {}", goal_id);
        }
    }
}
client := dakera.NewClient("http://localhost:3300", "dk-...")
ctx := context.Background()

// Create a goal
client.StoreMemory(ctx, "productivity-agent-user-1", dakera.StoreMemoryRequest{
    Content:    "GOAL: Launch SaaS product to 1000 paying customers. Deadline: 2024-09-30. Priority: critical.",
    Importance: 0.93,
    MemoryType: "working",
    Tags:       []string{"type:goal", "status:active", "priority:critical", "goal:q3-product-launch"},
    Metadata: map[string]interface{}{
        "goal_id":      "q3-product-launch",
        "status":       "active",
        "priority":     "critical",
        "deadline":     "2024-09-30",
        "progress_pct": 0,
    },
})

// Recall active goals at session start
goals, _ := client.Recall(ctx, "productivity-agent-user-1", dakera.RecallRequest{
    Query:         "active goals objectives priorities deadlines",
    TopK:          15,
    MinImportance: 0.70,
})

// Build proactive context
today := time.Now()
for _, mem := range goals.Memories {
    if status, ok := mem.Metadata["status"].(string); ok && status == "active" {
        if deadline, ok := mem.Metadata["deadline"].(string); ok {
            d, _ := time.Parse("2006-01-02", deadline)
            daysRemaining := int(d.Sub(today).Hours() / 24)
            if daysRemaining <= 14 {
                fmt.Printf("[URGENT] Goal %s: %d days remaining
", mem.Metadata["goal_id"], daysRemaining)
            }
        }
    }
}

Give your agent a persistent memory for what matters most

Goal tracking transforms reactive assistants into proactive partners that remember what you're working toward.

Try Free →

Real-World Scenario: Personal Productivity Agent

A productivity coaching agent serves knowledge workers who track quarterly objectives. The agent runs 5–10 sessions per user per week. With goal tracking implemented:

  • Session 1: User mentions "I need to get our API docs finished before the developer conference in 6 weeks." Agent detects goal statement, creates memory with goal_id="api-docs-conference", deadline="2024-08-15", priority="high".
  • Sessions 2–4: Each session, agent opens with: "You have 34 days until the developer conference. API docs are your top priority. Where are you on the reference section?" Proactive instead of passive.
  • Session 5: User says "I finished the quickstart section." Agent calls update_goal_progress() with progress_pct=35 and milestone note. Responds with encouragement and prompts next section.
  • Day 10 before deadline: Goal surfaces as urgent. Agent opens with: "10 days until the developer conference. Docs are at 70%. Based on your pace, you need 2 more focused sessions to finish. Can we plan them now?"
  • Outcome: Users with goal tracking enabled completed 78% of stated objectives vs. 41% without (measured over 90-day cohort).

Before / After Memory State

Before: No Goal Memory
// Memory store: empty

// Session 8:
User: "Still working on
the API docs."

Agent: "What API docs?
Can you tell me more
about your project?"

// User has to re-explain
// context from scratch.
// No progress awareness.
// No deadline urgency.
// No proactive suggestions.
After: Goal Tracking Active
// Memory store:
{
  "content": "GOAL: API docs for developer conference",
  "importance": 0.93,
  "metadata": {
    "goal_id": "api-docs-conference",
    "status": "active",
    "deadline": "2024-08-15",
    "progress_pct": 70
  }
}
// Session 8:
Agent: "10 days until the
conference. You're at 70%.
Let's finish the auth section
today — that's your biggest
remaining chunk."

SDK Method Reference

MethodSDKPurpose in this pattern
store_memory(agent_id, content, importance, memory_type, tags)PythonCreate goal, sub-goal, or progress update memory
recall(agent_id, query, top_k, min_importance)PythonRetrieve active goals at session start
storeMemory(agentId, {content, importance, memoryType, tags})TypeScriptPersist goal with status metadata
recall(agentId, query, {top_k, min_importance})TypeScriptRecall active goals with urgency filter
client.store_memory("agent", StoreMemoryRequest{...}).await?RustAsync goal storage with tags
client.recall("agent", RecallRequest{...}).await?RustAsync goal recall with min_importance
client.StoreMemory(ctx, "agent", StoreMemoryRequest{...})GoStore goal with deadline metadata
client.Recall(ctx, "agent", RecallRequest{...})GoRecall active goals for session context

Edge Cases and Gotchas

  • Goal collision — contradictory objectives: A user says "I want to grow aggressively" in Q1 and "I need to cut costs" in Q2. Both are stored as active goals but are in conflict. Add a conflict detection step: when storing a new goal, recall existing goals and check for semantic contradiction (ask the LLM to evaluate). Flag conflicts for explicit user resolution rather than silently keeping both.
  • Informal progress statements: "I've been working on it" is not the same as "I finished it." Be conservative with progress updates — only update progress_pct when the user explicitly states a quantified milestone ("I'm 80% done", "I finished the first section"). Vague progress statements should be stored as episodic notes, not goal updates.
  • Goal drift: A user's goal changes subtly over time — "launch a product" becomes "launch a specific product to a specific market." Don't overwrite the original goal memory; store updates that reference the original goal_id. The evolution of a goal is itself valuable context.
  • Orphaned sub-goals: If a parent goal is abandoned, its sub-goals should also be abandoned. When you call abandon_goal(), also recall and abandon any memories with parent_goal_id matching the abandoned goal. Orphaned active sub-goals create confusing context at session start.
  • Privacy and sensitive goals: Some user goals are sensitive — health targets, relationship goals, financial struggles. Tag these with a sensitivity:high metadata flag and ensure they are excluded from any cross-user analytics or logging systems. Consider a separate namespace for sensitive goal categories.
Warning: Don't over-surface goals in every response

Proactive goal references are powerful when timed well, but annoying when constant. Don't mention active goals in every single response — it feels robotic. Instead, surface them at natural trigger points: session start, when the user asks for help with a related topic, when a deadline is approaching, or when the user explicitly asks for a status check.

Performance Considerations

~14ms
Active goal recall (top_k=15)
0.93
Recommended importance for active goals
78%
Goal completion rate with tracking vs 41% without
Advanced Configuration: Goal templates and recurring objectives

For agents that manage recurring goals (weekly check-ins, quarterly reviews), use a template system that creates fresh goal instances from patterns:

GOAL_TEMPLATES = {
    "weekly_review": {
        "content_template": "WEEKLY GOAL: Review progress on {parent_goal} for week of {week_start}",
        "importance": 0.85,
        "ttl_seconds": 604_800,  # 7 days -- weekly goals auto-expire
        "memory_type": "working"
    },
    "quarterly_okr": {
        "content_template": "OKR Q{quarter}: {objective} — key results: {key_results}",
        "importance": 0.95,
        "ttl_seconds": 7_776_000,  # 90 days
        "memory_type": "working"
    }
}

def create_from_template(agent_id: str, template_name: str, **kwargs) -> str:
    template = GOAL_TEMPLATES[template_name]
    content = template["content_template"].format(**kwargs)
    goal_id = slugify(content[:50])
    client.store_memory(
        agent_id=agent_id,
        content=content,
        importance=template["importance"],
        memory_type=template["memory_type"],
        tags=["type:goal", "status:active", f"template:{template_name}"],
        ttl_seconds=template["ttl_seconds"]
    )
    return goal_id

Build agents that remember what your users are working toward

Goal tracking with Dakera turns reactive chatbots into proactive productivity partners. Get started free.

Start Building Free →