Goal Tracking
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 →- 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: 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: 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 messagesLook 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 metadataGoals get importance 0.90–0.95 — they must always surface during recall. Include
status,priority,deadline,goal_id, andprogress_pctin metadata. Usememory_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 startOpen each session with a
recall()call: query"active goals objectives priorities deadlines",top_k=15,min_importance=0.7. Filter results tostatus=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_idWhen a user mentions a sub-task related to an existing goal, store it with
parent_goal_idin metadata. Sub-goals get importance 0.80–0.90. When recalling the parent goal, also query for sub-goals using thegoal_idas search context to reconstruct the full hierarchy. -
Update goal status without deleting historyWhen 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.
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.
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()withprogress_pct=35and 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
// 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.
// 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
| Method | SDK | Purpose in this pattern |
|---|---|---|
store_memory(agent_id, content, importance, memory_type, tags) | Python | Create goal, sub-goal, or progress update memory |
recall(agent_id, query, top_k, min_importance) | Python | Retrieve active goals at session start |
storeMemory(agentId, {content, importance, memoryType, tags}) | TypeScript | Persist goal with status metadata |
recall(agentId, query, {top_k, min_importance}) | TypeScript | Recall active goals with urgency filter |
client.store_memory("agent", StoreMemoryRequest{...}).await? | Rust | Async goal storage with tags |
client.recall("agent", RecallRequest{...}).await? | Rust | Async goal recall with min_importance |
client.StoreMemory(ctx, "agent", StoreMemoryRequest{...}) | Go | Store goal with deadline metadata |
client.Recall(ctx, "agent", RecallRequest{...}) | Go | Recall 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_pctwhen 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 withparent_goal_idmatching 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:highmetadata flag and ensure they are excluded from any cross-user analytics or logging systems. Consider a separate namespace for sensitive goal categories.
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
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 →