32 KiB
status, system, requirements, created, owners
| status | system | requirements | created | owners | ||
|---|---|---|---|---|---|---|
| current | tasks |
|
2026-08-09 |
|
Task Dependencies and Auto-Start Chains System Design
Purpose and boundaries
This design record preserves the technical source for the capability mapped to REQ-TASKS-TASK-DEPENDENCIES-001 while the task system completes its migration.
Requirement mapping
| Requirement | Design source |
|---|---|
| REQ-TASKS-TASK-DEPENDENCIES-001 | Migrated legacy design detail below |
Migrated design source
Why
A user who splits work into ordered steps — "scaffold the schema", then "write the API on top of it", then "wire the UI" — has no way to express that order in Kandev. The parent/child subtask hierarchy says "B is part of A", never "B cannot start until A finishes". So the user either starts everything at once and lets agents collide in the same repository, or babysits the board and presses Start by hand each time a predecessor lands. Both defeat the point of running agents unattended.
What
- A task SHALL be able to declare that it depends on one or more other
tasks. The relationship is peer-to-peer and independent of
parent_id: a dependency may exist between two unrelated tasks, between siblings, or between a task and its own child. - The dependency graph is a general DAG, not a linear chain. A task may have any number of predecessors and any number of dependents. Cycles SHALL be rejected at edge-creation time with the offending path surfaced to the user.
- A task with at least one unresolved dependency is blocked. Blocked SHALL be visible on the Kanban card, in the task detail view, and in the task API payload.
- A blocked task SHALL NOT be auto-started by any automated path — workflow
on_enter: auto_start_agent, WIP queue promotion, integration watchers, or dependency resolution itself. The gate applies to every automated launch, not only to the dependency-resolution path. - A user MAY still start a blocked task manually. Manual start is an explicit override, not a bug; the UI names it as such.
- A task MAY carry the intent "start automatically when unblocked". When every dependency of such a task resolves successfully, Kandev launches it exactly once with the agent profile, executor, executor profile, prompt, and plan-mode choice recorded when the intent was created.
- Setting that intent on each link of a chain produces sequential execution: A finishes → B starts → B finishes → C starts. No separate "chain" entity exists; a chain is a path through the DAG.
- Auto-start on unblock respects every existing admission rule. A task whose
dependencies have resolved but which is not WIP-admitted (
wip_admitted = false) stays queued and launches when the WIP queue promotes it. Dependency resolution grants eligibility, never a bypass. - A dependency resolves only on successful completion of the predecessor.
FAILED,CANCELLED, and archival do not resolve a dependency. The dependent task stays blocked and its blocked reason changes to name the failed predecessor, so a chain never proceeds on a failed step. - A dependency-failed state SHALL raise a notification and SHALL require explicit human action to clear (retry the predecessor, remove the edge, or start the dependent manually). Kandev never auto-retries a failed predecessor and never silently drops the edge.
- Users declare dependencies in the task-create dialog ("Depends on") and
manage them afterwards from the Edit task dialog or over MCP. The edit
dialog behavior is defined in
task-dependency-detail-editing.md. - An open task SHALL show a dependency chip in the status row directly above its chat composer, alongside the PR status chip. The chip reports both directions — the tasks this task is blocked by, and the tasks it blocks — and opens to a list of them with their states, each navigable to that task. It is absent when the task has no edges in either direction.
- Agents SHALL be able to declare and manage dependencies over MCP: set them
when creating a task, and add or remove them on an existing task. The tools
SHALL teach when a dependency is the right relationship rather than a subtask,
because an agent that reaches for
parent_idto express ordering gets neither the gate nor the chain. - Creating a task with dependencies and an agent-start request SHALL record the start as a start-when-unblocked intent rather than launching immediately. An automated caller that asks for both is describing a chain step, not a task to run now.
- Deleting a task removes its edges in both directions. Dependents whose last
unresolved dependency was the deleted task become unblocked, and a
start-when-unblockedintent on such a dependent SHALL NOT fire — deletion is not success.
Relationship to existing mechanisms
This feature deliberately extends four shipped mechanisms rather than adding parallel ones. The reuse is part of the contract because the guarantees below (single launch, restart survival, WIP interaction) are inherited from them.
| Existing mechanism | Role here |
|---|---|
task_blockers table + BFS cycle detection (office/dashboard.AddTaskBlocker) |
The dependency edge store and cycle validator, promoted from Office-only to a core task relationship. |
Deferred launch intent (tasks.metadata.deferred_launch, claimed atomically) |
Storage and idempotent consumption of "start automatically when unblocked". |
Automated launch guards (orchestrator.autoStartTaskForStep and orchestrator.launchStart) |
autoStartTaskForStep gates workflow, queue, watcher, and dependency-resolution starts before claims. launchStart gates LaunchSession auto-starts, including session.ensure, and downgrades blocked requests to workspace-only prepare. |
WIP admission and queue promotion (tasks/wip-limit-pull-system) |
Decides whether an unblocked, auto-start-intent task launches now or on promotion. |
Two existing behaviors are intentionally not changed:
- Parent/child completion signalling (
tasks/subtask-completion-trigger) fireson_children_completedwhen all children reach any terminal state, includingFAILED. Dependency resolution uses a stricter definition (success only). The two must not be unified. - Office's blocker reaction (
office/scheduler.cascadeBlockersResolved) queues an Office run to the blocked task's assignee when a blocker's status becomes done. It stays as-is. For a task that qualifies for both reactions, the shared auto-start claim guarantees exactly one launch.
Data model
task_blockers (existing, reused)
task_id TEXT the dependent task (blocked)
blocker_task_id TEXT the predecessor task (blocking)
created_at TIMESTAMP
PRIMARY KEY (task_id, blocker_task_id)
CHECK (task_id != blocker_task_id)
- The row reads "
task_idis blocked byblocker_task_id". - Edges are workspace-local: both tasks MUST belong to the same workspace.
- No new column is added. Edge deletion on task delete/archive is enforced by
the owning repository, not by a SQLite
ON DELETE CASCADE, because the table predates the foreign key. - The table currently ships from the Office repository's
createTaskExtensionTables. Reading and writing it becomes a core task concern; the Office reader methods stay valid against the same rows.
tasks.metadata.deferred_launch (existing, reused)
The start-when-unblocked intent is a deferred_launch metadata record on the
dependent task, identical in shape to the one WIP overflow already
persists: resolved agent profile, executor, executor profile, prompt,
plan-mode choice, priority, and attachments. It is claimed atomically before
launch and restored on launch failure.
A task therefore has at most one launch intent regardless of how many predecessors it has. "Which edge triggers the start" is not a question this model can ask — the answer is always "the last one to resolve".
Derived state (not persisted)
blocked, blocked_reason, and the predecessor list are computed from
task_blockers plus the predecessors' own state on every read. There is no
denormalized is_blocked column: a stale copy of it would gate launches
incorrectly, which is the one failure this feature must not have.
A predecessor is resolved when its persisted state is COMPLETED.
Task completion owns the explicit step-entry setting
and compatibility backfill. Step name, position, or membership alone does not
resolve a dependency. A conversation follow-up does not create another
dependency-completion cycle.
A predecessor is failed when state is FAILED or CANCELLED.
A predecessor is pending in every other case, including archived.
API surface
HTTP
Existing Office-scoped routes are promoted to task-scoped equivalents. The Office paths remain for compatibility with the Office task pane.
POST /api/v1/tasks/:id/dependencies {"depends_on_task_id": "<id>"}
DELETE /api/v1/tasks/:id/dependencies/:depId
POSTreturns409with{"error": "...", "cycle": ["A","B","C","A"]}when the edge would close a cycle. Thecyclebody shape matches whatBlockersPickeralready parses.POSTreturns400for a self-edge or a cross-workspace edge.DELETEof an absent edge is a success no-op.
Task payload
Task DTOs returned over HTTP, WebSocket boot, WebSocket events, and MCP gain:
{
"blocked": true,
"blocked_reason": "pending",
"depends_on": [
{ "id": "task-a", "title": "Add the schema migration", "state": "COMPLETED", "status": "resolved" },
{ "id": "task-b", "title": "Expose the edges on the API", "state": "TODO", "status": "pending" }
],
"blocks": [{ "id": "task-d", "title": "Render the chip", "state": "TODO" }],
"start_when_unblocked": true
}
blocked_reasonis one of"pending"(at least one predecessor unfinished),"failed"(no predecessor unfinished, at least one failed),"unknown"(the dependency store could not be read, so the gate failed closed), or omitted whenblockedis false. A client renders"unknown"as blocked without a named cause; it must not present it as resolved.depends_onlists direct predecessors only, increated_atorder.blockslists direct dependents only, increated_atorder. It exists so the dependency chip can show both directions without a second round trip, and so an agent can see what is waiting on it.- Both lists carry enough per-entry detail for the chip to render without fetching each task: id, title, and state. Neither list is transitive.
- The projection is absent, not empty, when a payload does not compute it.
Every boot payload and list read computes it; lightweight
task.updatedevents do not. A client that reads an omitted projection as "no edges" erases the real edges, so absent MUST mean "unknown" and an explicit empty list MUST mean "no edges". A reader that needs certainty re-reads the task. start_when_unblockedreflects the presence of a launch intent tied to dependency resolution. It is read-only on the task payload; it is set through the create request or the dependency picker.
Task create
POST /api/v1/tasks already accepts blocked_by: ["<id>", …]. It gains:
{ "blocked_by": ["task-a"], "start_when_unblocked": true }
When start_when_unblocked is true and blocked_by is non-empty, the
create request's agent/executor/prompt resolution is recorded as the deferred
launch intent instead of launching now.
start_when_unblocked: true with an empty blocked_by records no intent. The
task is born unblocked, so there is no later moment at which it would fire; a
create that also asked to start an agent launches immediately, exactly as it
would without the flag, and one that did not launches nothing. The flag defers
a start, it never invents one. Adding a dependency to that task afterwards
gates its automated starts but does not retroactively create an intent.
When blocked_by is non-empty and start_when_unblocked is omitted, it defaults
to the request's agent-start intent: a create that asked to start an agent
records the intent, and a create that did not records no intent. The response
reports started and start_when_unblocked so the caller never has to infer
which happened. This rule is what makes an automated caller's habitual
start_agent: true build a chain instead of launching every step at once.
WebSocket events
task.updatedwithfields: ["dependencies"]on edge add/remove.task.dependencies_resolved— published when the last unresolved dependency of a task resolves successfully. Payload:{task_id, resolved_by_task_id}. This is the event whose handler enters the auto-start chokepoint.task.dependency_failed— published when a predecessor of a blocked task reachesFAILEDorCANCELLED. Payload:{task_id, failed_task_id, failed_state}.
Notifications
A new notification event type task.dependency_failed follows the existing
constants in internal/notifications/service. It fires once per
(dependent, predecessor) failure transition.
MCP
create_task_kandev's handler already reads a blocked_by array from its
payload, but the tool schema never declares it, so no agent can discover or pass
it. Declaring it is the minimum; the tools below make chains expressible.
create_task_kandevdeclaresblocked_by(array of task IDs) andstart_when_unblocked(boolean).blocked_byplusstart_agent: truerecords a start-when-unblocked intent and launches nothing now.start_agentdefaults to true and every agent passes it by habit, so the alternative — launching a blocked task immediately — would silently break every chain an agent tries to build. The create response reportsstarted: falsewithstart_when_unblocked: trueso the caller can see what happened. Passingstart_when_unblocked: falsealongsideblocked_bycreates the edges with no launch intent at all.add_task_dependency_kandev—{task_id?, depends_on_task_id}.task_iddefaults to the calling task. Returns the resultingdepends_onlist. Rejects a cycle with the cycle path, a self-edge, and a cross-workspace edge.remove_task_dependency_kandev—{task_id?, depends_on_task_id}. Removing an absent edge succeeds. Removing the last edge unblocks the task without launching it.list_related_tasks_kandevis the read side and already returnsblockersandblocked_by. Those groups become populated in Kanban-only installs, where they are empty today because the blocker repository is wired inside the Office-enabled branch.- Tool descriptions SHALL state when to use a dependency instead of a subtask: a
subtask means "part of", a dependency means "not until". Decomposing a plan
into ordered phases is N sibling tasks chained with
blocked_byandstart_when_unblocked, not N subtasks started at once. The description SHALL also state that a failed predecessor halts the chain and needs human action, so an agent does not build a chain expecting it to self-heal. - An agent cannot set
start_when_unblockedon a task it did not create; it uses the dependency tools for edges and leaves launch intent to the task's owner.
State machine
Dependency state is derived, so the machine below describes the dependent task's gating status rather than a persisted column.
edge added predecessor resolved (not last)
unblocked ──────────────────────> blocked ──────────────────────────> blocked
^ │ ^ │
│ last edge removed │ │ predecessor reopened │
│ or last predecessor resolved │ │ │
└──────────────────────────────┘ └──────────────────────────────────┘
│
predecessor FAILED/CANCELLED
v
blocked (reason=failed)
│
predecessor retried and resolved, OR edge removed
v
unblocked
Transitions and their actors:
| From | To | Trigger | Actor |
|---|---|---|---|
| unblocked | blocked | edge created | user, agent (blocked_by), API |
| blocked | blocked | a predecessor resolves, others remain | system |
| blocked | blocked (reason=failed) | a predecessor enters FAILED/CANCELLED |
system |
| blocked | unblocked | last unresolved predecessor resolves successfully | system |
| blocked | unblocked | last blocking edge removed | user, agent, API |
| unblocked | blocked | a resolved predecessor is reopened to a non-terminal state | user, system |
On the blocked -> unblocked transition caused by successful resolution, and
only then, Kandev publishes task.dependencies_resolved and evaluates
auto-start:
task.dependencies_resolved
-> task has deferred launch intent? no -> stop (task is merely unblocked)
-> task is WIP-admitted? no -> stop; WIP promotion will retry
-> claim deferred launch intent lost -> stop; the winner launches
-> launch with the recorded parameters
Removing the last edge unblocks the task but does not fire auto-start: the intent is consumed by dependency resolution, not by the absence of dependencies. A user who removes the edge is taking manual control.
Permissions
- Creating or removing an edge requires the same authorization as updating both tasks; both must be in a workspace the caller can access. Cross-workspace edges are rejected, so no new cross-tenant surface exists.
- Agents create edges through task creation (
blocked_by), the two MCP tools (add_task_dependency_kandev,remove_task_dependency_kandev), and the existing Office blocker endpoints. They cannot setstart_when_unblockedon a task they did not create. - Every one of those paths authorizes BOTH task IDs against the caller's
identity before any read or write, and denials surface as not-found so an
edge attempt cannot be used to probe for another user's tasks. In a
task-bound MCP session the dependent end is the session's own task, taken
from the
AgentExecutionrather than from an agent-supplied identifier; the predecessor is necessarily another task and is authorized on its own. - The dependency gate is not a permission: a user with start rights can start a blocked task manually.
Failure modes
| Condition | Required behavior |
|---|---|
| Edge would create a cycle | 409 with the cycle path; no row written. |
| Edge is a self-edge or crosses workspaces | 400; no row written. |
Predecessor enters FAILED/CANCELLED |
Dependent stays blocked with blocked_reason: "failed"; task.dependency_failed published; notification raised; no launch. |
| Predecessor is archived while a dependent is blocked | Treated as pending, not resolved. The dependent stays blocked and the reason names the archived predecessor. |
| Predecessor is deleted | Edge is removed. The dependent may become unblocked but its launch intent does not fire. |
| Dependent has no deferred launch intent | It unblocks and waits for a user or for its step's normal on_enter behavior on a later move. |
| Dependent is not WIP-admitted when its dependencies resolve | No launch. The existing WIP promotion path launches it once capacity opens; the intent survives. |
| Dependent's step has a WIP limit that is full | Same as above — the dependency gate and the WIP gate are independent and both must pass. |
| Two paths race to launch the same dependent (dependency resolution and WIP promotion) | The existing atomic deferred-launch claim admits one. The loser logs and stops. |
| Launch fails after the claim | The claim is restored so the task remains retryable and visibly unstarted, matching the WIP-overflow contract. |
| Predecessor lookup fails while evaluating resolution | No launch, warning logged. A later state change on any predecessor re-evaluates. |
| Blocker repository is unavailable | Edge writes fail with an error. Reads that fail return the task as blocked with reason unknown rather than unblocked — the gate fails closed, because failing open would launch work whose predecessor may not have run. |
| A predecessor is reopened after the dependent already launched | No retroactive stop. The dependent shows blocked again; the running session is untouched. |
| Dependency graph is large | Cycle detection keeps its existing BFS walk limit; a walk that exceeds the limit rejects the edge rather than accepting an unverified one. |
Persistence guarantees
- Dependency edges are durable and survive restart.
- Start-when-unblocked intents are durable (task metadata) and survive restart, including across a restart that happens between a predecessor's completion and the dependent's launch: startup reconciliation re-evaluates blocked tasks with intents and launches those whose dependencies are satisfied.
- Derived
blockedstate is never persisted and is recomputed on read, so a restart cannot resurrect a stale gate. - An intent is consumed at most once. A restart between claim and launch leaves the task unstarted with the claim restored, not double-started.
task.dependencies_resolvedandtask.dependency_failedare in-memory events and are not replayed after restart; the startup reconciliation above is the recovery path, and a failed-predecessor state is re-derived on read.
Scenarios
Edges and blocking:
- GIVEN tasks A and B in the same workspace, WHEN the user adds "B
depends on A", THEN B's card shows a blocked badge naming one
predecessor and B's payload reports
blocked: true,blocked_reason: "pending",depends_on: ["A"]. - GIVEN "B depends on A", WHEN the user tries to add "A depends on B",
THEN the request is rejected with the cycle path
A → B → Aand no edge is created. - GIVEN "B depends on A" and "C depends on B", WHEN the user tries to
add "A depends on C", THEN the request is rejected with the cycle path
A → C → B → A. - GIVEN tasks in two different workspaces, WHEN the user tries to link them, THEN the request is rejected and no edge is created.
- GIVEN B depends on A and on X, WHEN A completes successfully,
THEN B remains blocked with
depends_on: ["A","X"]andblocked_reason: "pending".
Gating:
- GIVEN B depends on an unfinished A and B's workflow step has
on_enter: auto_start_agent, WHEN B is moved into that step or its task view sendssession.ensure, THEN no agent starts. The move creates no session; focus may create only a workspace-readyCREATEDsession. - GIVEN B depends on an unfinished A and B is queued for a WIP-limited auto-start step, WHEN capacity opens and B is promoted, THEN B is admitted, its queued badge clears, and no session is created.
- GIVEN B depends on an unfinished A, WHEN the user clicks Start on B, THEN B starts, because manual start is an explicit override.
Auto-start and chaining:
- GIVEN B depends on A, B has a start-when-unblocked intent, and B is WIP-admitted, WHEN A completes successfully, THEN exactly one session is created for B using the intent's recorded agent profile, executor, executor profile, prompt, and plan-mode choice.
- GIVEN A → B → C each with a start-when-unblocked intent, WHEN A completes, THEN B starts; WHEN B completes, THEN C starts, and A is never restarted.
- GIVEN B depends on A and B has no start-when-unblocked intent, WHEN A completes, THEN B unblocks, no session is created, and B's card shows no blocked badge.
- GIVEN B depends on A, B has an intent, and B is queued (not WIP-admitted) for a full step, WHEN A completes, THEN no session is created and B stays queued; WHEN capacity later opens and B is promoted, THEN exactly one session is created.
- GIVEN B depends on A and B has an intent, WHEN A completes and the backend restarts before B launches, THEN after startup exactly one session is created for B.
- GIVEN B depends on A and on X, both with A and X unfinished, WHEN A completes and then X completes, THEN B launches exactly once, on X's completion.
- GIVEN B depends on A and B has an intent, WHEN the user removes the edge instead of A completing, THEN B unblocks and no session is created.
Failure paths:
- GIVEN B depends on A and B has an intent, WHEN A ends
FAILED, THEN B stays blocked withblocked_reason: "failed", no session is created,task.dependency_failedis published, and a notification is raised. - GIVEN B is blocked with
blocked_reason: "failed"after A failed, WHEN A is retried and completes successfully, THEN B unblocks and its intent fires exactly once. - GIVEN B depends on A and A is
CANCELLED, WHEN the dependency state is read, THEN B is blocked withblocked_reason: "failed". - GIVEN B depends on A, WHEN A is archived, THEN B stays blocked and A is reported as a pending predecessor.
- GIVEN B depends on A and B has an intent, WHEN A is deleted, THEN the edge is removed, B unblocks, and no session is created.
- GIVEN A has completed and B has launched, WHEN A is reopened to
IN_PROGRESS, THEN B reports blocked again and B's running session is not stopped. - GIVEN the blocker store returns an error, WHEN an automated path evaluates whether a task may auto-start, THEN the launch is skipped and a warning is logged.
UI:
- GIVEN a blocked task on the Kanban board, WHEN the user hovers or taps its blocked badge, THEN the predecessor titles and their states are listed, and the badge coexists with the WIP queued badge without truncating either.
- GIVEN the task-create dialog, WHEN the user opens "Depends on", THEN the board's other tasks are offered as candidates, selecting one shows a removable chip, and the copy states that starting an agent will run the task automatically once every selection completes.
- GIVEN a task created with dependencies and an agent start, WHEN creation
succeeds, THEN no session is started and the response reports
start_when_unblocked: true. - GIVEN an open task B where B depends on A and D depends on B, WHEN the task view renders, THEN a dependency chip appears in the status row above the composer next to the PR status chip, and opening it lists A under blocked-by and D under blocks, each with its state and each navigable.
- GIVEN an open task with no dependency edges in either direction, WHEN the task view renders, THEN no dependency chip appears, and a status row that would otherwise be empty still does not render.
- GIVEN an open task whose board copy carries no dependency projection (a
lightweight
task.updatedoverwrote it, or arrived before hydration), WHEN the task view renders, THEN the chip still shows the task's edges, because a reader that cannot trust its cached copy re-reads them. - GIVEN an open blocked task whose predecessor failed, WHEN the user opens the dependency chip, THEN the failed predecessor is distinguished from merely pending ones.
- GIVEN a narrow mobile viewport, WHEN the user taps the dependency chip, THEN the list opens in the same drawer pattern the PR status chip uses on mobile, not a hover card.
MCP:
- GIVEN an agent calls
create_task_kandevwithblocked_by: ["A"]and no explicitstart_agent, THEN the task is created with the edge, no session is started, and the response reportsstarted: falseandstart_when_unblocked: true. - GIVEN an agent calls
create_task_kandevwithblocked_by: ["A"]andstart_when_unblocked: false, THEN the edge is created, no launch intent is recorded, and completing A starts nothing. - GIVEN an agent calls
create_task_kandevwithstart_when_unblocked: trueand noblocked_by, THEN no launch intent is recorded and the task starts or does not start purely perstart_agent; the response reportsstart_when_unblocked: false. - GIVEN an agent decomposes a plan into three chained sibling tasks over
three
create_task_kandevcalls, WHEN the first task completes successfully, THEN the second starts and the third does not. - GIVEN an agent calls
add_task_dependency_kandevomittingtask_id, THEN the edge is added to the calling task and the resultingdepends_onlist is returned. - GIVEN an agent calls
add_task_dependency_kandevwith an edge that would close a cycle, THEN the call fails with the cycle path and no edge is created. - GIVEN an agent calls
remove_task_dependency_kandevfor an edge that does not exist, THEN the call succeeds. - GIVEN a Kanban-only install with
Features.Officefalse, WHEN an agent callscreate_task_kandevwithblocked_by, THEN the task and edge are created andlist_related_tasks_kandevreports them. - GIVEN a narrow mobile viewport, WHEN the user opens a column containing a blocked task, THEN the blocked state and predecessor count are readable on the card without hover-only disclosure and without horizontal overflow.
Out of scope
-
A whole-graph board view. A layered DAG view was built and then removed before shipping: without drawn connectors it read as columns of text, and it could only show one workflow at a time, so it did not answer "show me this chain" any better than the per-task chip does. The chip (both directions, one hop) and
list_related_tasks_kandevare the shipped ways to read the graph. A future view should draw real connectors, using the existingcomponents/kanban/graph2-connector.tsxprimitives, and be able to focus a single chain. -
Office mode changes. Office is feature-flagged and not in the live feature inventory (
docs/features.md, "In Progress: Office Mode"), and it already has its own blocker reaction and run queue. v1 makes the relationship and the Kanban reaction work in Kanban mode and leavescascadeBlockersResolved, Office agent assignment, and Office task delegation untouched. Unifying the two reactions is a follow-up that should wait until Office ships. -
Optional or "soft" dependencies (start-anyway-after-N-hours, OR semantics across predecessors). v1 is AND-only over all direct predecessors.
-
Cross-workspace dependencies.
-
Passing a predecessor's output (summary, diff, PR link) into the dependent's prompt. The dependent launches with the prompt recorded in its intent. Agents that need predecessor context read it through
list_related_tasks_kandev. -
Stopping or rolling back a dependent that already launched when its predecessor is reopened or reverted.
-
Automatic retry of a failed predecessor, and automatic edge removal on failure.
-
A dependency-aware scheduling policy (critical-path ordering, priority inheritance along a chain, WIP reservation for a whole chain).
-
Templated chains — saving "A → B → C" as a reusable pipeline definition. A chain is a set of edges between concrete tasks in v1.
-
Gantt or timeline rendering, and estimated-duration modelling.
-
Editing dependencies from the multi-select toolbar or via bulk operations.