Execution Semantics for Issue Dependencies and Blockers in Paperclip AI
Paperclip AI implements issue dependencies as a directed-acyclic graph where blockedBy edges enforce hard execution constraints, with workflowSort providing topological ordering and the attention service generating actionable items for stalled blockers.
This article examines how Paperclip AI encodes, sorts, and acts on issue blocker relationships across its TypeScript codebase. The execution semantics span three architectural layers: shared type definitions, UI presentation logic, and server-side attention generation.
Data Model: Encoding Blocker State and Diagnostics
The foundation of Paperclip's execution semantics resides in [packages/shared/src/types/issue.ts](https://github.com/paperclipai/paperclip/blob/master/packages/shared/src/types/issue.ts). This file defines the canonical shape of blocker attention diagnostics that flow through the entire system.
IssueBlockerAttentionState Enum
The state field uses a four-value enum to communicate blocker urgency:
"none"– no active dependency concern"covered"– blocker exists but is being addressed"stalled"– blocker is stuck and requires intervention"needs_attention"– blocker demands immediate human action
Key Diagnostic Fields
The IssueBlockerAttention interface (lines 399–414) carries:
| Field | Purpose |
|---|---|
reason |
Why the blocker exists (e.g., "active_dependency") |
terminalBlockerIssueId |
The leaf blocker requiring human action |
blockingTreeLive |
Boolean indicating whether any descendant is already running |
These fields enable both the UI and server to make precise decisions about when and how to surface blocker-related actions.
Topological Sorting: The workflowSort Algorithm
The UI must present issues in an order that preserves dependency chains. The [ui/src/lib/workflowSort.ts](https://github.com/paperclipai/paperclip/blob/master/ui/src/lib/workflow-sort.ts) utility implements a chain-aware topological sort that keeps linear blocker sequences contiguous.
Core Algorithm Structure
The workflowSort function builds two auxiliary structures:
// From workflowSort.ts lines 90–104
const successors = new Map<string, string[]>(); // issueId → array of blockers
const inDegree = new Map<string, number>(); // count of incoming blocker edges
It then performs a chain walk: starting from each root, it follows the successors map as long as exactly one successor exists. This produces contiguous chains for the common case of A ← B ← C dependencies.
Fallback Behaviors
The algorithm includes defensive measures for edge cases:
-
Cross-parent blockers – When a chain branches (more than one successor or predecessor), the walk breaks and ordering falls back to timestamp/id tie-break (see the
if (succIds.length !== 1) break;guard). -
Missing blockers – Issues referencing non-existent blockers still display the blocker chip but receive no special ordering treatment (comment lines 14–15).
-
Cycle detection – If the output size doesn't match input size after sorting, the function degrades to a pure timestamp/id sort (lines 106–108), preventing infinite loops when the API's acyclicity validation fails.
The overall complexity is O(N + E) time, suitable for large boards.
Attention Generation: From Blocker State to Action
The server-side [attention.ts](https://github.com/paperclipai/paperclip/blob/master/server/src/services/attention.ts) service bridges blocker diagnostics and user-facing actions. Located in [server/src/services/attention.ts](https://github.com/paperclipai/paperclip/blob/master/server/src/services/attention.ts#L1444-L1526), it queries blocked issues and produces attention items—structured recommendations for the UI.
Query and Candidate Selection
Lines 44–82 fetch all issues with status: "blocked". For each, the service examines blockerAttention.state to determine severity.
Terminal Blocker Handling
Lines 108–154 implement the critical logic for stalled or needs-attention blockers:
// Simplified from attention.ts lines 108–154 and 322–355
if (blockerAttention?.state === "stalled" && !blockerAttention.blockingTreeLive) {
add(createItem({
sourceKind: "blocker_attention",
subject: issueSubject(prefix, terminalSummary),
whyNow: `Blocks ${blockedTaskCount} tasks and needs human attention.`,
decisionVerbs: decisionVerbs(
{ id: "unblock", label: "Unblock", description: "Repair the blocker" },
{ id: "reassign", label: "Reassign", description: "Assign to a live owner" },
{ id: "nudge", label: "Nudge", description: "Prompt the current owner" }
),
severity: "high"
}));
}
The terminalBlockerIssueId field ensures users act on the actual leaf blocker rather than intermediate nodes in a dependency chain.
Execution Semantics Summary Table
| Situation | System Behavior | Implementation Location |
|---|---|---|
| Issue A blocked by B | A cannot exit "blocked" status until B resolves |
blockedBy field + issuesSvc.update |
| Linear chain A←B←C | UI renders A → B → C contiguously | workflowSort.ts lines 90–104 |
| Cross-parent blocker | Chain walk breaks; timestamp fallback | workflowSort.ts successor guard |
| Stalled blocker | High-severity attention item with Unblock/Reassign/Nudge verbs |
attention.ts lines 108–154 |
| Missing blocker reference | Blocker chip displays; no ordering special case | workflowSort.ts comment lines 14–15 |
| Cycle in dependencies | Degrade to timestamp/id sort | workflowSort.ts lines 6–8, 106–108 |
Practical Code Examples
Creating a Blocker Relationship
// Client → server: establish dependency
await issuesService.update(issueId, {
status: "blocked",
blockedByIssueIds: [blockerId] // Must belong to same company
});
Sorting Issues with Blocker Awareness
import { workflowSort } from "@/lib/workflowSort";
const sorted = workflowSort(issues); // Issue[] from API
// Preserves linear chains, falls back to creation time otherwise
Generating Attention Items Server-Side
The attention service pattern shows how to surface actionable intelligence from blocker state—checking blockerAttention.state, validating !blockingTreeLive, and constructing decision verbs for the UI.
Key Implementation Files
Summary
- Safety: The
blockedByfield enforces hard dependencies—no issue advances while blockers remain unresolved. - Predictability:
workflowSortmaintains contiguous blocker chains so users perceive dependency structure clearly. - Actionability: The
attentionservice transformsstalled/needs_attentionstates into specific UI verbs (Unblock,Reassign,Nudge). - Resilience: Edge cases (missing blockers, cycles, cross-parent dependencies) degrade gracefully without system failure.
These semantics operate across the full stack—from type definitions in packages/shared through UI ordering in ui/src/lib to server intelligence in server/src/services.
Frequently Asked Questions
How does Paperclip prevent circular dependencies between issues?
The API layer validates acyclicity before persisting blockedByIssueIds. As a defensive measure, workflowSort detects size mismatches post-sort (lines 106–108) and falls back to timestamp-based ordering if cycles somehow reach the client.
What happens when a blocker issue is deleted?
The blockedBy reference persists but becomes a phantom blocker. The UI still renders the blocker chip (per workflowSort.ts comment lines 14–15), but the sorting algorithm treats it as absent—no special chain positioning applies.
Can multiple issues block a single dependent issue?
Yes. However, workflowSort only produces contiguous chains for linear dependencies (single successor, single predecessor). When an issue has multiple blockers, the chain walk breaks and ordering falls back to timestamp/id tie-break.
What triggers a "needs_attention" blocker state?
The attention service (lines 44–82) identifies blockers where human intervention is required—typically when blockingTreeLive is false and diagnostics indicate the blocker is not being actively addressed. This generates high-severity attention items with specific resolution verbs.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →