Dependencies create relationships between elements, enabling task orchestration, knowledge graphs, and workflow management.
Dependency Types
Stoneforge defines four categories of dependencies:
Blocking Affects work readiness
blocks
parent-child
awaits
Tasks cannot proceed until blockers resolve.
Associative Knowledge graph connections
relates-to
references
supersedes
duplicates
caused-by
validates
mentions
Informational only, no blocking.
Attribution Links elements to entities
authored-by
assigned-to
approved-by
Tracks ownership and responsibility.
Threading Message conversations Builds threaded discussions.
Blocking Dependencies
Blocking dependencies prevent tasks from being worked on until dependencies resolve.
blocks - Sequential Execution
Use for: Tasks that must complete in order
Direction gotcha: blocks works opposite to intuition!sf dependency add --type=blocks A B means:
A is blocked BY B
B must complete first
B is the blocker, A is blocked
// Task B must complete before Task A can start
await api . addDependency ({
blockerId: taskBId , // Completes first (the blocker)
blockedId: taskAId , // Waits (the blocked)
type: 'blocks' ,
createdBy: directorId ,
});
// Task A is now automatically marked as BLOCKED
const taskA = await api . get ( taskAId );
console . log ( taskA . status ); // 'blocked'
Example workflow:
// Create tasks
const design = await api . create ({
type: 'task' ,
title: 'Design database schema' ,
status: 'in_progress' ,
createdBy: directorId ,
});
const implement = await api . create ({
type: 'task' ,
title: 'Implement API endpoints' ,
status: 'open' ,
createdBy: directorId ,
});
const tests = await api . create ({
type: 'task' ,
title: 'Write integration tests' ,
status: 'open' ,
createdBy: directorId ,
});
const deploy = await api . create ({
type: 'task' ,
title: 'Deploy to staging' ,
status: 'open' ,
createdBy: directorId ,
});
// Add blocking relationships
await api . addDependency ({
blockerId: design . id ,
blockedId: implement . id ,
type: 'blocks' ,
createdBy: directorId ,
});
await api . addDependency ({
blockerId: implement . id ,
blockedId: tests . id ,
type: 'blocks' ,
createdBy: directorId ,
});
await api . addDependency ({
blockerId: tests . id ,
blockedId: deploy . id ,
type: 'blocks' ,
createdBy: directorId ,
});
// Only 'design' is ready for work
const readyTasks = await api . ready ();
// Returns: [design]
// Complete design task
await api . updateStatus ( design . id , { status: 'closed' });
// Now 'implement' becomes ready
const readyTasks2 = await api . ready ();
// Returns: [implement]
parent-child - Hierarchical Blocking
Use for: Subtasks that must complete before parent can close
// Parent task
const parent = await api . create ({
type: 'task' ,
title: 'Implement authentication system' ,
createdBy: directorId ,
});
// Child tasks
const child1 = await api . create ({
type: 'task' ,
title: 'Add login endpoint' ,
createdBy: directorId ,
});
const child2 = await api . create ({
type: 'task' ,
title: 'Add logout endpoint' ,
createdBy: directorId ,
});
const child3 = await api . create ({
type: 'task' ,
title: 'Add session management' ,
createdBy: directorId ,
});
// Create parent-child relationships
await api . addDependency ({
blockerId: child1 . id ,
blockedId: parent . id ,
type: 'parent-child' ,
createdBy: directorId ,
});
await api . addDependency ({
blockerId: child2 . id ,
blockedId: parent . id ,
type: 'parent-child' ,
createdBy: directorId ,
});
await api . addDependency ({
blockerId: child3 . id ,
blockedId: parent . id ,
type: 'parent-child' ,
createdBy: directorId ,
});
// Parent is blocked until all children complete
const parentTask = await api . get ( parent . id );
console . log ( parentTask . status ); // 'blocked'
Transitive blocking: Parent-child relationships propagate. If a grandchild is blocked, the grandparent is also blocked.
awaits - External Gate Dependency
Use for: Tasks waiting on external events or approvals
Timer Gate
Approval Gate
External Gate
Webhook Gate
Wait until a specific time: await api . addDependency ({
blockerId: gateElementId ,
blockedId: taskId ,
type: 'awaits' ,
createdBy: directorId ,
metadata: {
gateType: 'timer' ,
waitUntil: '2026-03-15T09:00:00.000Z' ,
},
});
// Task is blocked until March 15, 9 AM
Wait for approvals from specific entities: await api . addDependency ({
blockerId: gateElementId ,
blockedId: deployTaskId ,
type: 'awaits' ,
createdBy: directorId ,
metadata: {
gateType: 'approval' ,
requiredApprovers: [ teamLeadId , securityLeadId ],
approvalCount: 2 , // Need both
currentApprovers: [],
},
});
// Add approval
await api . update ( gateElementId , {
metadata: {
... gate . metadata ,
currentApprovers: [ teamLeadId ],
},
});
// Still blocked (need 2 approvals)
// Add second approval
await api . update ( gateElementId , {
metadata: {
... gate . metadata ,
currentApprovers: [ teamLeadId , securityLeadId ],
},
});
// Now unblocked
Wait for external system confirmation: await api . addDependency ({
blockerId: gateElementId ,
blockedId: taskId ,
type: 'awaits' ,
createdBy: directorId ,
metadata: {
gateType: 'external' ,
externalSystem: 'jenkins' ,
externalId: 'build-1234' ,
satisfied: false ,
},
});
// External system marks as satisfied
await api . update ( gateElementId , {
metadata: {
... gate . metadata ,
satisfied: true ,
satisfiedAt: new Date (). toISOString (),
satisfiedBy: systemEntityId ,
},
});
Wait for webhook callback: await api . addDependency ({
blockerId: gateElementId ,
blockedId: taskId ,
type: 'awaits' ,
createdBy: directorId ,
metadata: {
gateType: 'webhook' ,
webhookUrl: 'https://api.example.com/webhook/abc123' ,
callbackId: 'payment-confirmed' ,
satisfied: false ,
},
});
Associative Dependencies
Associative dependencies create knowledge graph connections without blocking work.
relates-to - Bidirectional Link
Use for: Semantic relationships between elements
Special behavior: relates-to is bidirectional - both directions are automatically linked.
// Link two related tasks
await api . addDependency ({
blockerId: taskAId ,
blockedId: taskBId ,
type: 'relates-to' ,
createdBy: directorId ,
});
// Query in either direction
const relatedToA = await api . getDependencies ( taskAId , 'relates-to' );
// Returns: [taskB]
const relatedToB = await api . getDependencies ( taskBId , 'relates-to' );
// Returns: [taskA]
Example use case:
// Link authentication tasks to security audit
const authTasks = [
'task-login' ,
'task-logout' ,
'task-session' ,
'task-password-reset' ,
];
const securityAudit = 'task-security-audit' ;
for ( const taskId of authTasks ) {
await api . addDependency ({
blockerId: taskId ,
blockedId: securityAudit ,
type: 'relates-to' ,
createdBy: directorId ,
});
}
// Find all security-related tasks
const relatedTasks = await api . getDependencies (
securityAudit ,
'relates-to'
);
references - Citation Link
Use for: Documents or tasks that reference others
// API documentation references implementation
await api . addDependency ({
blockerId: implementationDocId ,
blockedId: apiDocId ,
type: 'references' ,
createdBy: directorId ,
});
// Unidirectional - only apiDoc references implementationDoc
supersedes - Version Chain
Use for: Document or task versioning
// New version supersedes old
await api . addDependency ({
blockerId: oldDocId ,
blockedId: newDocId ,
type: 'supersedes' ,
createdBy: directorId ,
});
// Find latest version
const allVersions = await api . getDependents ( oldDocId , 'supersedes' );
const latest = allVersions . sort (( a , b ) =>
new Date ( b . createdAt ). getTime () - new Date ( a . createdAt ). getTime ()
)[ 0 ];
duplicates - Deduplication Marker
Use for: Marking duplicate tasks or issues
// Mark task as duplicate
await api . addDependency ({
blockerId: originalTaskId ,
blockedId: duplicateTaskId ,
type: 'duplicates' ,
createdBy: directorId ,
});
// Close duplicate
await api . updateStatus ( duplicateTaskId , {
status: 'closed' ,
closeReason: 'Duplicate of original task' ,
});
caused-by - Audit Trail
Use for: Root cause analysis
// Bug caused by specific feature
await api . addDependency ({
blockerId: featureTaskId ,
blockedId: bugTaskId ,
type: 'caused-by' ,
createdBy: directorId ,
});
// Find all bugs caused by a feature
const causedBugs = await api . getDependents (
featureTaskId ,
'caused-by'
);
validates - Test Verification
Use for: Linking tests to implementations
await api . addDependency ({
blockerId: implementationTaskId ,
blockedId: testTaskId ,
type: 'validates' ,
createdBy: directorId ,
metadata: {
testType: 'integration' ,
result: 'pass' ,
details: '142/142 tests passed' ,
},
});
mentions - @mention Links
Use for: Linking messages to mentioned entities
// Message mentions a user
await api . addDependency ({
blockerId: entityId ,
blockedId: messageId ,
type: 'mentions' ,
createdBy: senderId ,
});
// Find all messages mentioning an entity
const mentions = await api . getDependents ( entityId , 'mentions' );
Blocked Status Computation
CRITICAL: The blocked status is computed from dependencies, never set manually!
How Blocking Works
A task is blocked if ANY of these conditions are true:
Has blocking dependency with unclosed blocker
// Task B blocked by Task A
blockingDep = { blockerId: taskA , blockedId: taskB , type: 'blocks' }
taskA . status !== 'closed' → taskB is BLOCKED
Has parent-child dependency with unclosed children
// Parent blocked by any unclosed child
parentChildDep = { blockerId: child , blockedId: parent , type: 'parent-child' }
child . status !== 'closed' → parent is BLOCKED
Has awaits dependency with unsatisfied gate
// Task blocked by unsatisfied gate
awaitsDep = { blockerId: gate , blockedId: task , type: 'awaits' }
! gate . metadata . satisfied → task is BLOCKED
Blocked Cache
For performance, blocked status is materialized:
CREATE TABLE blocked_cache (
element_id TEXT PRIMARY KEY ,
is_blocked INTEGER NOT NULL ,
updated_at TEXT NOT NULL
);
Recomputed when:
Dependency added/removed
Blocker task status changes
Gate satisfaction changes
// Check if task is blocked
const task = await api . get ( taskId );
if ( task . status === 'blocked' ) {
// Find blockers
const blockers = await api . getDependencies ( taskId , [
'blocks' ,
'parent-child' ,
'awaits' ,
]);
console . log ( 'Blocked by:' , blockers );
}
Cycle Detection
Cycles are forbidden for blocking dependencies. They would create deadlocks.
What is a cycle?
Task A blocks B, B blocks C, C blocks A → Circular dependency!
Cycle Detection Algorithm
import { DependencyService } from '@stoneforge/quarry' ;
const depService = new DependencyService ( storage );
// Before adding dependency, check for cycles
const wouldCreateCycle = depService . detectCycle ({
blockerId: taskCId ,
blockedId: taskAId ,
type: 'blocks' ,
});
if ( wouldCreateCycle ) {
throw new Error ( 'Cannot add dependency - would create cycle' );
}
await api . addDependency ({
blockerId: taskCId ,
blockedId: taskAId ,
type: 'blocks' ,
createdBy: directorId ,
});
api.addDependency() does not automatically check for cycles. Use DependencyService.detectCycle() explicitly.
Querying Dependencies
Get Dependencies (What blocks this?)
// Get all dependencies
const deps = await api . getDependencies ( taskId );
// Get specific types
const blockers = await api . getDependencies ( taskId , [ 'blocks' , 'parent-child' ]);
// Get blocking dependencies only
const blockingDeps = await api . getDependencies ( taskId )
. then ( deps => deps . filter ( d => isBlockingDependency ( d )));
Get Dependents (What does this block?)
// Get all dependents
const dependents = await api . getDependents ( taskId );
// Get tasks blocked by this task
const blockedTasks = await api . getDependents ( taskId , 'blocks' );
// Get children (for parent-child)
const children = await api . getDependents ( parentId , 'parent-child' );
Traverse Dependency Graph
// Find all transitive blockers
function getAllBlockers ( taskId : ElementId , visited = new Set ()) : ElementId [] {
if ( visited . has ( taskId )) return [];
visited . add ( taskId );
const directBlockers = await api . getDependencies ( taskId , [
'blocks' ,
'parent-child' ,
]);
const transitive = [];
for ( const blocker of directBlockers ) {
transitive . push ( blocker . blockerId );
transitive . push ( ... getAllBlockers ( blocker . blockerId , visited ));
}
return transitive ;
}
const allBlockers = await getAllBlockers ( taskId );
console . log ( `Task blocked by ${ allBlockers . length } tasks` );
Dependency Visualization
Generate Mermaid Diagram
function generateDependencyGraph ( tasks : Task []) : string {
const lines = [ 'graph TD' ];
for ( const task of tasks ) {
const deps = await api . getDependencies ( task . id , 'blocks' );
for ( const dep of deps ) {
const blocker = tasks . find ( t => t . id === dep . blockerId );
if ( blocker ) {
lines . push ( ` ${ blocker . title } -->|blocks| ${ task . title } ` );
}
}
}
return lines . join ( ' \n ' );
}
const diagram = await generateDependencyGraph ( tasks );
console . log ( diagram );
Find Critical Path
function findCriticalPath ( taskId : ElementId ) : Task [] {
const path : Task [] = [];
let current = taskId ;
while ( current ) {
const task = await api . get ( current );
path . push ( task );
const deps = await api . getDependencies ( current , 'blocks' );
if ( deps . length === 0 ) break ;
// Find longest remaining path
const depths = await Promise . all (
deps . map ( d => getDepth ( d . blockerId ))
);
const maxDepthIndex = depths . indexOf ( Math . max ( ... depths ));
current = deps [ maxDepthIndex ]. blockerId ;
}
return path . reverse ();
}
const criticalPath = await findCriticalPath ( finalTaskId );
console . log ( 'Critical path:' , criticalPath . map ( t => t . title ));
Best Practices
1. Remember blocks direction
// WRONG: A does NOT block B
await api . addDependency ({
blockerId: taskAId ,
blockedId: taskBId ,
type: 'blocks' ,
createdBy: directorId ,
});
// This means B blocks A!
// CORRECT: A blocks B (B must wait for A)
await api . addDependency ({
blockerId: taskBId , // B is the blocker
blockedId: taskAId , // A is blocked
type: 'blocks' ,
createdBy: directorId ,
});
2. Check for cycles before adding
const depService = new DependencyService ( storage );
if ( depService . detectCycle ({ blockerId , blockedId , type: 'blocks' })) {
throw new Error ( 'Would create cycle' );
}
await api . addDependency ({ blockerId , blockedId , type: 'blocks' , createdBy });
3. Use appropriate dependency types
// Sequential: use blocks
await api . addDependency ({ type: 'blocks' , ... });
// Hierarchical: use parent-child
await api . addDependency ({ type: 'parent-child' , ... });
// External gate: use awaits
await api . addDependency ({ type: 'awaits' , ... });
// Related work: use relates-to (non-blocking)
await api . addDependency ({ type: 'relates-to' , ... });
4. Never set status='blocked' manually
// WRONG: Never do this
await api . update ( taskId , { status: 'blocked' });
// CORRECT: Add blocking dependency
await api . addDependency ({
blockerId: blockerTaskId ,
blockedId: taskId ,
type: 'blocks' ,
createdBy: directorId ,
});
// Status becomes 'blocked' automatically
5. Query both directions for relates-to
// relates-to is bidirectional
const relatedForward = await api . getDependencies ( taskId , 'relates-to' );
const relatedReverse = await api . getDependents ( taskId , 'relates-to' );
const allRelated = [ ... relatedForward , ... relatedReverse ];
Next Steps
Task Management See how dependencies affect task status
Orchestration Loop Learn how blocked tasks are handled
Workflows Build multi-step task sequences
Agent Roles Understand who creates dependencies