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 its goals, keeps the ones still active (including any nearing their deadlines) and constructs a proactive context note. Progress and status changes are stored as new memories with the same goal_id, creating an audit trail; the agent reads the latest one for each goal. Dakera stores, ranks and returns these memories; deadlines, urgency and the goal hierarchy are logic in your agent.
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.93, well above the 0.5 default, so they rank near the top of a goal recall. Include
status,priority,deadline,goal_id, andprogress_pctin metadata. Store them asmemory_type="semantic": the memory type sets how fast decay works (semantic decays at half the default speed, working at three times it), not the recall order. 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. Keep results withstatus=activeand drop anygoal_idthat a later memory marks completed or abandoned; take progress from the latestPROGRESS UPDATE. Compute days remaining for any goal with a deadline. Surface any goal with 14 days or less 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.87. 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. Use
memory_type="semantic"and importance 0.82 for completed goals (0.65 for abandoned ones), below the 0.93 of active 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:3000/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": "semantic",
"tags": ["type:goal", "status:active", "priority:critical", "goal:q3-product-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:3000/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 goal: q3-product-launch.",
"importance": 0.87,
"memory_type": "semantic",
"tags": ["type:goal", "status:active", "goal:beta-testing"],
"metadata": {
"goal_id": "beta-testing",
"parent_goal_id": "q3-product-launch",
"status": "active",
"deadline": "2024-08-31",
"progress_pct": 60
}
}'
# Recall active goals (session start)
curl -X POST http://localhost:3000/v1/memory/recall \
-H "Authorization: Bearer dk-..." \
-H "Content-Type: application/json" \
-d '{
"agent_id": "productivity-agent-user-1",
"query": "active goals objectives priorities deadlines",
"top_k": 15,
"min_importance": 0.7
}'
# Mark goal completed (store new memory, don't delete old)
curl -X POST http://localhost:3000/v1/memory/store \
-H "Authorization: Bearer dk-..." \
-H "Content-Type: application/json" \
-d '{
"agent_id": "productivity-agent-user-1",
"content": "GOAL COMPLETED [q3-product-launch]: Launched SaaS product. Reached 1,240 paying customers by September 28, 2024, 2 days early.",
"importance": 0.82,
"memory_type": "semantic",
"tags": ["type:goal", "status:completed", "goal:q3-product-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:3000", 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="semantic",
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 goals, still above the 0.7 recall threshold
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 active goals with their latest progress and urgency."""
response = client.recall(
agent_id=agent_id,
query="active goals objectives priorities deadlines current tasks",
top_k=15,
min_importance=0.70
)
records = [m.metadata or {} for m in response.memories]
# A goal is closed once any memory with its goal_id records completed or abandoned
closed = {r.get("goal_id") for r in records if r.get("status") in ("completed", "abandoned")}
# The latest PROGRESS UPDATE per goal_id carries the current progress
progress = {}
for r in sorted((r for r in records if "progress_pct" in r and "updated_at" in r), key=lambda r: r["updated_at"]):
progress[r["goal_id"]] = r["progress_pct"]
goals = []
today = date.today()
for meta in records:
if meta.get("status") != "active" or meta.get("goal_id") in closed:
continue
goal = {
"goal_id": meta.get("goal_id"),
"priority": meta.get("priority", "medium"),
"progress_pct": progress.get(meta.get("goal_id"), 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 "\n".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:3000', 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,
memory_type: 'semantic',
tags: ['type:goal', 'status:active', `priority:${opts.priority ?? 'medium'}`, `goal:${goalId}`],
metadata: {
goal_id: goalId,
parent_goal_id: opts.parentGoalId ?? null,
status: 'active',
priority: opts.priority ?? 'medium',
deadline: opts.deadline ?? null,
progress_pct: 0,
},
});
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,
memory_type: 'episodic',
tags: ['type:goal-progress', `goal:${goalId}`],
metadata: { goal_id: goalId, progress_pct: progressPct, updated_at: new Date().toISOString() },
});
}
async function completeGoal(agentId: string, goalId: string, outcome: string): Promise<void> {
await client.storeMemory(agentId, {
content: `GOAL COMPLETED [${goalId}]: ${outcome}`,
importance: 0.82,
memory_type: 'semantic',
tags: ['type:goal', 'status:completed', `goal:${goalId}`],
metadata: { goal_id: goalId, status: 'completed', completed_at: new Date().toISOString() },
});
}
interface ActiveGoal {
goalId: string;
priority: Priority;
progressPct: number;
daysRemaining?: number;
urgent: boolean;
}
async function getActiveGoals(agentId: string): Promise<ActiveGoal[]> {
const { memories } = await client.recall(
agentId,
'active goals objectives priorities deadlines current tasks',
{ top_k: 15, min_importance: 0.7 }
);
const records = memories.map(m => (m.metadata ?? {}) as Record<string, any>);
// Closed once any memory with the goal_id records completed or abandoned
const closed = new Set(records.filter(r => r.status === 'completed' || r.status === 'abandoned').map(r => r.goal_id));
// Latest progress update per goal_id
const progress = new Map<string, number>();
records
.filter(r => r.progress_pct !== undefined && r.updated_at)
.sort((a, b) => String(a.updated_at).localeCompare(String(b.updated_at)))
.forEach(r => progress.set(r.goal_id, r.progress_pct));
const today = new Date();
return records
.filter(r => r.status === 'active' && !closed.has(r.goal_id))
.map(r => {
const daysRemaining = r.deadline
? Math.ceil((new Date(r.deadline).getTime() - today.getTime()) / 86_400_000)
: undefined;
return {
goalId: r.goal_id as string,
priority: (r.priority as Priority) ?? 'medium',
progressPct: progress.get(r.goal_id) ?? r.progress_pct ?? 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_client::{DakeraClient, MemoryType, RecallRequest, StoreMemoryRequest};
let client = DakeraClient::builder("http://localhost:3000")
.api_key("dk-...")
.build()?;
// Create a goal (status and goal_id travel as tags)
client.store_memory(
StoreMemoryRequest::new(
"productivity-agent-user-1",
"GOAL: Launch SaaS product to 1000 paying customers. Deadline: 2024-09-30. Priority: critical.",
)
.with_type(MemoryType::Semantic)
.with_importance(0.93)
.with_tags(vec![
"type:goal".into(),
"status:active".into(),
"priority:critical".into(),
"goal:q3-product-launch".into(),
]),
).await?;
// Recall goals at session start
let goals = client.recall(
RecallRequest::new("productivity-agent-user-1", "active goals objectives priorities deadlines")
.with_top_k(15)
.with_min_importance(0.7),
).await?;
for goal in &goals.memories {
if goal.content.starts_with("GOAL:") || goal.content.starts_with("SUB-GOAL:") {
println!("Goal: {} (score {:.2})", goal.content, goal.score);
}
}client := dakera.NewClientWithOptions(dakera.ClientOptions{
BaseURL: "http://localhost:3000",
APIKey: "dk-...",
})
ctx := context.Background()
// Create a goal (status and goal_id travel as tags)
importance := float32(0.93)
_, err := client.StoreMemory(ctx, "productivity-agent-user-1", dakera.StoreMemoryRequest{
Content: "GOAL: Launch SaaS product to 1000 paying customers. Deadline: 2024-09-30. Priority: critical.",
MemoryType: "semantic",
Importance: &importance,
Tags: []string{"type:goal", "status:active", "priority:critical", "goal:q3-product-launch"},
})
// Recall goals at session start
minImportance := float32(0.7)
goals, err := client.Recall(ctx, "productivity-agent-user-1", dakera.RecallRequest{
Query: "active goals objectives priorities deadlines",
TopK: 15,
MinImportance: &minImportance,
})
// Build proactive context from the goal memories
for _, mem := range goals.Memories {
if strings.HasPrefix(mem.Content, "GOAL:") || strings.HasPrefix(mem.Content, "SUB-GOAL:") {
fmt.Printf("Goal: %s (score %.2f)\n", mem.Content, mem.Score)
}
}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?"
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"
}
}
{
"content": "PROGRESS UPDATE [api-docs-conference]: 70% complete.",
"metadata": {"goal_id": "api-docs-conference", "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, metadata) | 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, memory_type, tags, metadata}) | TypeScript | Persist goal with status metadata |
recall(agentId, query, {top_k, min_importance}) | TypeScript | Recall active goals with urgency filter |
client.store_memory(StoreMemoryRequest::new(agent, content).with_tags(...)).await? | Rust | Async goal storage with tags |
client.recall(RecallRequest::new(agent, query).with_top_k(15).with_min_importance(0.7)).await? | Rust | Async goal recall at session start |
client.StoreMemory(ctx, "agent", dakera.StoreMemoryRequest{...}) | Go | Store goal with status tags |
client.Recall(ctx, "agent", dakera.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 separateagent_idfor sensitive goal categories: each agent's memories live in their own namespace. - Decay while the user is away: Dakera lowers the importance of memories that are not used (default half-life 168 hours; semantic memories decay at half that speed), and a recall restarts the clock. After a long absence, goals can fall below
min_importance=0.7. To load every goal regardless of importance, use filter-based batch recall on thetype:goaltag (POST /v1/memories/recall/batch), which needs no query, and filterstatus=activein your code.
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": "semantic" # decays at half speed; working decays 3x faster
}
}
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}"],
metadata={"goal_id": goal_id, "status": "active"},
ttl_seconds=template["ttl_seconds"] # deleted at expiry
)
return goal_id
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.