Loom includes a decision graph system for tracking goals, decisions, actions, and observations across agent sessions. This provides persistent context and enables agents to learn from past decisions.
Overview
The decision tracking system uses a graph database to store:
Goals - High-level objectives
Decisions - Choices made during execution
Options - Alternative approaches considered
Actions - Steps taken to achieve goals
Outcomes - Results of actions
Observations - Learnings and insights
Revisits - Re-evaluations of previous decisions
Nodes are connected with typed edges (leads_to, blocks, enables, etc.) to represent relationships.
decision_log
Logs a decision, goal, action, or observation to the decision graph.
Type of decision node. Valid types:
goal - High-level objective
decision - Choice or determination
option - Alternative approach
action - Concrete step taken
outcome - Result of an action
observation - Learning or insight
revisit - Re-evaluation of previous decision
Short, descriptive title for this node
Detailed description of the decision, goal, or observation
Confidence level from 0-100. Use for decisions and options to indicate certainty.
ID of parent node to connect via edge. Creates a relationship in the graph.
Type of edge to create to parent. Common types:
leads_to - Sequential relationship
blocks - Prevents or conflicts with
enables - Makes possible
requires - Depends on
Returns
Success response includes:
Node type and title
Generated node ID
Edge information if parent was linked
Logged goal: Implement user authentication (id: 550e8400-e29b-41d4-a716-446655440000)
With parent link:
Logged action: Add auth middleware (id: ...), linked to 550e8400-... via leads_to
Usage Examples
Log a goal
Log a decision with parent
Log an observation
Track alternative options
Action with outcome
{ :ok , result} = Loom . Tools . DecisionLog . run (
%{
node_type: "goal" ,
title: "Implement user authentication" ,
description: "Add JWT-based auth to API endpoints" ,
confidence: 90
},
%{ session_id: "session-123" , project_path: "/project" }
)
# Extract ID from result for linking
goal_id = "550e8400-e29b-41d4-a716-446655440000"
Decision Graph Patterns
Classic workflow for tracking implementation: # 1. Define goal
{ :ok , goal} = log_node ( "goal" , "Add caching layer" )
# 2. Make decision
{ :ok , decision} = log_node (
"decision" ,
"Use ETS for cache" ,
parent_id: goal.id
)
# 3. Take action
{ :ok , action} = log_node (
"action" ,
"Implement cache module" ,
parent_id: decision.id
)
Decision with Multiple Options
Track alternatives considered: # Log decision
{ :ok , decision} = log_node ( "decision" , "Choose database" )
# Log option A (chosen)
log_node (
"option" ,
"PostgreSQL" ,
confidence: 90 ,
parent_id: decision.id,
edge_type: "enables"
)
# Log option B (rejected)
log_node (
"option" ,
"MySQL" ,
confidence: 60 ,
parent_id: decision.id,
edge_type: "blocks"
)
Observations and Learnings
Document insights for future reference: log_node (
"observation" ,
"ETS cache reduced API latency by 60%" ,
description: "Measured over 1000 requests. P99 latency dropped from 150ms to 60ms."
)
Mark decisions that need reconsideration: { :ok , original_decision_id} = find_decision ( "Use ETS cache" )
log_node (
"revisit" ,
"Consider distributed cache" ,
description: "ETS doesn't work across nodes. Need Redis or Cachex." ,
parent_id: original_decision_id
)
decision_query
Query the decision graph for active goals, recent decisions, pulse reports, or search by keyword.
Type of query to run:
active_goals - List goals with status “active”
recent_decisions - Most recent decision nodes
pulse - Generate summary report
search - Search by keyword in title/description
Search term for search query type. Matches title and description fields.
Maximum results to return
Query Types
active_goals
Retrieve all goals with status “active”.
{ :ok , result} = Loom . Tools . DecisionQuery . run (
%{ query_type: "active_goals" },
context
)
Returns:
Active Goals:
- [goal] Implement user authentication (confidence: 90%) (active, id: 550e8400-...)
- [goal] Add rate limiting (confidence: 75%) (active, id: 660e9500-...)
recent_decisions
Get the most recent decision nodes (sorted by insertion time).
Loom . Tools . DecisionQuery . run (
%{
query_type: "recent_decisions" ,
limit: 5
},
context
)
Returns:
Recent Decisions:
- [decision] Use Guardian for JWT (confidence: 85%) (completed, id: ...)
- [decision] Use PostgreSQL for storage (confidence: 90%) (completed, id: ...)
- [decision] Deploy to Fly.io (confidence: 80%) (active, id: ...)
pulse
Generate a summary report of the decision graph state.
Loom . Tools . DecisionQuery . run (
%{ query_type: "pulse" },
context
)
Returns:
Decision Graph Pulse Report:
Active Goals: 3
Recent Decisions: 12
Pending Actions: 5
Completed Outcomes: 8
Top confidence decisions:
- Use PostgreSQL for storage (90%)
- Implement user authentication (90%)
- Deploy to Fly.io (85%)
Low confidence areas:
- Choice of caching strategy (45%)
search
Search for nodes by keyword in title or description.
Loom . Tools . DecisionQuery . run (
%{
query_type: "search" ,
search_term: "authentication" ,
limit: 10
},
context
)
Returns:
Search Results for 'authentication':
- [goal] Implement user authentication (confidence: 90%) (active, id: ...)
- [decision] Use Guardian for JWT (confidence: 85%) (completed, id: ...)
- [action] Add authentication middleware (completed, id: ...)
- [observation] Auth tests passed successfully (completed, id: ...)
Usage Examples
Check active work
Search for context
Generate status report
# See what goals are currently active
{ :ok , goals} = DecisionQuery . run (
%{ query_type: "active_goals" },
context
)
# Review recent decisions
{ :ok , decisions} = DecisionQuery . run (
%{ query_type: "recent_decisions" , limit: 10 },
context
)
Workflow Example
Complete example of using decision tracking in an agent workflow:
# 1. Check existing goals
{ :ok , goals} = DecisionQuery . run (
%{ query_type: "active_goals" },
context
)
# 2. Log new goal
{ :ok , goal_result} = DecisionLog . run (
%{
node_type: "goal" ,
title: "Add caching to API endpoints" ,
description: "Reduce database load and improve response times" ,
confidence: 85
},
context
)
goal_id = extract_id_from_result (goal_result)
# 3. Consider options
{ :ok , option1} = DecisionLog . run (
%{
node_type: "option" ,
title: "Use ETS for in-memory cache" ,
confidence: 70 ,
parent_id: goal_id
},
context
)
{ :ok , option2} = DecisionLog . run (
%{
node_type: "option" ,
title: "Use Redis for distributed cache" ,
confidence: 85 ,
parent_id: goal_id
},
context
)
# 4. Make decision
{ :ok , decision_result} = DecisionLog . run (
%{
node_type: "decision" ,
title: "Use Redis with Redix client" ,
description: "Supports distributed caching across nodes. Well-maintained library." ,
confidence: 90 ,
parent_id: goal_id,
edge_type: "leads_to"
},
context
)
decision_id = extract_id_from_result (decision_result)
# 5. Take action
{ :ok , action_result} = DecisionLog . run (
%{
node_type: "action" ,
title: "Add Redix dependency and configure connection pool" ,
parent_id: decision_id,
edge_type: "leads_to"
},
context
)
# 6. Log outcome
DecisionLog . run (
%{
node_type: "outcome" ,
title: "Cache implemented successfully" ,
description: "Redis connection pool configured. Cache hit rate: 78%" ,
parent_id: extract_id_from_result (action_result),
edge_type: "leads_to"
},
context
)
# 7. Query for summary
{ :ok , pulse} = DecisionQuery . run (
%{ query_type: "pulse" },
context
)
Best Practices
Link Related Nodes Always use parent_id to connect related decisions. This creates a navigable graph.
Use Confidence Scores Add confidence levels to decisions and options to track certainty over time.
Document Alternatives Log rejected options with blocks edges to remember why they weren’t chosen.
Log Observations Capture learnings and metrics as observations for future reference.
Implementation Details
Decision tracking tools are implemented in lib/loom/tools/:
The graph is stored in Loom.Schemas.DecisionNode with edges in Loom.Schemas.DecisionEdge.