Skip to main content
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
  • replies-to
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
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

Associative Dependencies

Associative dependencies create knowledge graph connections without blocking work. 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'
);
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',
  },
});
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:
  1. 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
    
  2. 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
    
  3. Has awaits dependency with unsatisfied gate
    // Task blocked by unsatisfied gate
    awaitsDep = { blockerId: gate, blockedId: task, type: 'awaits' }
    !gate.metadata.satisfiedtask 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

// 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,
});
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 });
// 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', ... });
// 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
// 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

Build docs developers (and LLMs) love