Dependency Order Planner
workflow.topological_sort
Overview
You send the steps and which ones must finish before which, and we return one order where every prerequisite comes first, plus the levels of steps that can run in parallel. When several orders are equally correct, the tie-breaker you choose decides: the order you declared the steps in, or alphabetical. Anything that cannot be placed — a step in a dependency loop, a step waiting on an id you never declared, or a step waiting on one of those — comes back in blocked with the reason and the ids responsible, so no step is ever silently dropped. We work out the order and nothing else: we run no step and schedule none.
Every scheduler, build tool and migration runner needs the same answer — in what order may these run — and each one re-implements it slightly differently, usually dropping the steps it cannot place. One shared answer, with the blocked steps named, keeps that from being rediscovered per service.
Reliability
Fully deterministic: the same steps, constraints and tie-breaker always give the same order. LEXICOGRAPHIC makes the answer independent of how you listed the steps, which is what you want when the input comes from an unordered source. Traversal is iterative, so a 10000-step chain is ordered rather than overflowing. Failures, which are about the request rather than the graph: WORKFLOW_NODE_LIMIT_EXCEEDED and WORKFLOW_EDGE_LIMIT_EXCEEDED mean you sent more than 10000 steps or 50000 constraints, so split the plan. WORKFLOW_IDENTIFIER_INVALID means an id is empty, longer than 128 characters, padded with whitespace, or carries a control character; fix the id and resend. WORKFLOW_OUTPUT_TOO_LARGE means the ids alone would exceed 2 MB of response, which needs a plan at the ceiling with very long ids; shorten the ids or split the plan. Loops and dangling prerequisites are never failures — they come back as complete set to false.
Context footprint
Returns ids and counts, no step payloads: a 1000-step plan is a few tens of kilobytes, and each id appears once in ordered and once in levels. A successful call costs 1 execution credit.
When to use it
When you hold tasks and prerequisites and want a run order, parallel waves, and an explicit account of anything unschedulable.
When you need the run itself, when ordering should follow cost or priority instead of dependencies, or when only a valid-or-not verdict is wanted.
Scope
Non-goals
- Does not run or schedule anything
- Does not resolve a dependency loop for you
- Does not invent steps for prerequisites you never declared
- Does not estimate durations or pick a critical path
Properties
Capabilities
Cost & billing
Flat rate per invocation
File handling
How we handle your data
What this means
This skill may receive sensitive content and may return submitted, transformed, extracted, or reformatted content.
Squillify guarantees
- Runs inside Squillify
- This skill runs inside Squillify.
- Payloads are not sent to an external service by this skill.
- Squillify does not permanently store submitted payloads.
- Telemetry records metadata only, not submitted content.
- Submitted payloads are not used for training.
- Submitted payloads are not reviewed by humans.
- This skill does not target sensitive data.
- The response may contain submitted or transformed content.
Your responsibility
Squillify does not determine whether submitted content is sensitive. Your application is responsible for deciding what data it is allowed to submit. Your application is responsible for securing the response.
Input schema
| Field | Type | Required | Description |
|---|---|---|---|
| nodes | array | Yes | Steps to order, 0 to 10000 closed {id} objects counted once when duplicated. |
| dependencies | array | No | Ordering constraints, 0 to 50000 closed {before, after} objects; omit it so every step lands in the first level. |
| tieBreaker | string | No | How equal steps are ordered; omit it for INPUT_ORDER. |
Output schema
| Field | Type | Required | Description |
|---|---|---|---|
| complete | boolean | Yes | True only when every distinct step was placed and nothing was duplicated or dangling. |
| ordered | array | Yes | Run order of every step that could be placed, 0 to 10000 ids, safe to execute sequentially. |
| levels | array | Yes | The same steps grouped into parallel waves, 0 to 10000 waves; empty when nothing could be placed. |
| blocked | array | Yes | Steps that could not be placed, each a closed {id, reason, dependsOn} object in declaration order. |
| nodeCount | integer | Yes | How many distinct steps were declared. |
| dependencyCount | integer | Yes | How many distinct constraints remain after de-duplication. |
| tieBreaker | string | Yes | Tie-breaker actually applied, filled in with the default when you left it out. |
| issues | array | Yes | One closed {code, count, message} object per problem kind; empty when the order is complete. |
| _meta | object | Yes | Always present on success, with orderedCount, blockedCount, levelCount and declaredDependencyCount. |
API contract
POST /v1/executecurl -sS -X POST "$BASE/v1/execute" -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' -d '{"skillKey":"workflow.topological_sort","input":{"nodes":[{"id":"extract"},{"id":"load"}],"dependencies":[{"before":"extract","after":"load"}]}}'import { Squillify } from "@squillify/sdk";
const sq = new Squillify({ apiKey: process.env.SQUILLIFY_API_KEY });
const { output } = await sq.execute("workflow.topological_sort", {"dependencies":[{"before":"extract","after":"transform"},{"before":"fetch-rates","after":"transform"},{"before":"transform","after":"load"}],"nodes":[{"id":"extract"},{"id":"fetch-rates"},{"id":"transform"},{"id":"load"}]});Errors
| Code | HTTP | Retryable | Description |
|---|---|---|---|
| VALIDATION_ERROR | 400 | No | The request payload failed schema validation. |
| UNAUTHORIZED | 401 | No | Missing or invalid API key. |
| SKILL_NOT_ACCESSIBLE | 403 | No | The API key is not granted access to this skill. |
| SKILL_NOT_AVAILABLE_FOR_PLAN | 403 | No | This skill is not included in your current plan. |
| PLAN_ELIGIBILITY_UNAVAILABLE | 503 | Yes | Plan eligibility could not be decided. Retry after backoff. |
| SKILL_NOT_FOUND | 404 | No | The requested skill or version does not exist. |
| TENANT_RATE_LIMITED | 429 | Yes | Request rate limit exceeded. Retry after backoff. |
| QUOTA_EXCEEDED | 429 | Yes | Plan quota exhausted. Retryable once the quota period resets. |
| EXECUTION_TIMEOUT | 504 | Yes | Execution exceeded the allowed time budget. |
| WORKFLOW_NODE_LIMIT_EXCEEDED | 429 | No | A size, quota, rate, or time limit was exceeded. |
| WORKFLOW_EDGE_LIMIT_EXCEEDED | 429 | No | A size, quota, rate, or time limit was exceeded. |
| WORKFLOW_IDENTIFIER_INVALID | 400 | No | The request payload failed this skill's input validation. |
| WORKFLOW_OUTPUT_TOO_LARGE | 429 | No | A size, quota, rate, or time limit was exceeded. |
Other limits
| Limit | Value |
|---|---|
| Recommended inline bytes | 32 KiB |
Limitations
- Does not run or schedule anything
- Does not resolve a dependency loop for you
- Does not invent steps for prerequisites you never declared
- Does not estimate durations or pick a critical path
- Executing the plan
- Ordering by cost, duration or priority rather than dependency
- Graphs where you only need the validity verdict
How to call
POST /v1/execute (requires authentication)
{
"skillKey": "workflow.topological_sort",
"input": {
"dependencies": [
{
"before": "extract",
"after": "transform"
},
{
"before": "fetch-rates",
"after": "transform"
},
{
"before": "transform",
"after": "load"
}
],
"nodes": [
{
"id": "extract"
},
{
"id": "fetch-rates"
},
{
"id": "transform"
},
{
"id": "load"
}
]
}
}Examples
A four-step pipeline with two independent starts, ordered as declared
{
"dependencies": [
{
"before": "extract",
"after": "transform"
},
{
"before": "fetch-rates",
"after": "transform"
},
{
"before": "transform",
"after": "load"
}
],
"nodes": [
{
"id": "extract"
},
{
"id": "fetch-rates"
},
{
"id": "transform"
},
{
"id": "load"
}
]
}{
"blocked": [],
"ordered": [
"extract",
"fetch-rates",
"transform",
"load"
],
"nodeCount": 4,
"levels": [
[
"extract",
"fetch-rates"
],
[
"transform"
],
[
"load"
]
],
"dependencyCount": 3,
"issues": [],
"tieBreaker": "INPUT_ORDER",
"_meta": {
"orderedCount": 4,
"blockedCount": 0,
"levelCount": 3,
"declaredDependencyCount": 3
},
"complete": true
}A loop and a typo: two steps strand a third, and alphabetical order settles the rest
{
"nodes": [
{
"id": "beta"
},
{
"id": "alpha"
},
{
"id": "ring-a"
},
{
"id": "ring-b"
},
{
"id": "after-ring"
}
],
"dependencies": [
{
"before": "ring-a",
"after": "ring-b"
},
{
"before": "ring-b",
"after": "ring-a"
},
{
"before": "ring-b",
"after": "after-ring"
},
{
"before": "typo",
"after": "beta"
}
],
"tieBreaker": "LEXICOGRAPHIC"
}{
"blocked": [
{
"id": "beta",
"reason": "MISSING_DEPENDENCY",
"dependsOn": [
"typo"
]
},
{
"id": "ring-a",
"reason": "IN_CYCLE",
"dependsOn": [
"ring-b"
]
},
{
"id": "ring-b",
"reason": "IN_CYCLE",
"dependsOn": [
"ring-a"
]
},
{
"id": "after-ring",
"reason": "DEPENDS_ON_BLOCKED",
"dependsOn": [
"ring-b"
]
}
],
"ordered": [
"alpha"
],
"nodeCount": 5,
"levels": [
[
"alpha"
]
],
"dependencyCount": 4,
"issues": [
{
"code": "MISSING_REFERENCE",
"count": 1,
"message": "A dependency references an id that no node declares."
},
{
"code": "CYCLE",
"count": 1,
"message": "Nodes depend on each other in a loop, so no order can satisfy every dependency."
},
{
"code": "BLOCKED",
"count": 4,
"message": "Nodes could not be placed in the order; each one reports why and which identifiers are responsible."
}
],
"tieBreaker": "LEXICOGRAPHIC",
"_meta": {
"orderedCount": 1,
"blockedCount": 4,
"levelCount": 1,
"declaredDependencyCount": 4
},
"complete": false
}Recommended use cases
- Turn a set of tasks and prerequisites into a runnable order
- Group independent steps into waves you can execute in parallel
- Show an author which steps a loop or a typo has stranded
Avoid for
- Executing the plan
- Ordering by cost, duration or priority rather than dependency
- Graphs where you only need the validity verdict
Related skills
Composable with
Safety & privacy
- Runs without calling any external service.
- Your payloads are never stored permanently.
- Telemetry captures metadata only — never your payload contents.
- We never train models on your payloads.
- No human reviews your payloads.