Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ DevSpace is a self-hosted MCP server that lets ChatGPT read, edit, search, and r

The same `/mcp` endpoint serves the 2026-07-28 per-request protocol and automatically supports older 2025-era clients through stateless compatibility handling. There is no protocol mode to configure.

[Dynamic workflows](docs/dynamic-workflows.md) let a host write JavaScript to coordinate configured subagents in parallel, run pipelines, validate structured results, and inspect or control durable runs.

## Sponsors and Special Thanks
<!--

Expand Down
998 changes: 998 additions & 0 deletions docs/dynamic-workflows-implementation-plan.md

Large diffs are not rendered by default.

79 changes: 79 additions & 0 deletions docs/dynamic-workflows.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Dynamic workflows

DevSpace executes JavaScript that coordinates its configured subagents. The script contract includes `agent`, `parallel`, `pipeline`, `phase`, `log`, `args`, `budget`, and nested `workflow`. The same script can select different agent profiles or providers without changing its orchestration logic.

Enable workflows in your DevSpace `config.jsonc`, alongside the subagent providers you already use:

```jsonc
{
"workflows": {
"enabled": true,
"defaultAgentType": "reviewer",
"maxConcurrentAgents": 16,
"maxConcurrentRuns": 4
}
}
```

`reviewer` must be an existing configured profile. Run `devspace agents targets` to discover usable profiles and providers. Restart the MCP server after changing tool availability; an active execution daemon keeps its original configuration until its work finishes.

Save this example as `.devspace/workflows/review.js`:

```javascript
export const meta = {
name: "review",
description: "Review project areas and summarize findings",
phases: [{ title: "Review" }, { title: "Summarize" }]
};
phase("Review");
const findings = await parallel(args.areas.map(area => () =>
agent(`Review ${area}; identify concrete defects and include file references.`, {
label: area,
schema: {
type: "object",
properties: { findings: { type: "array", items: { type: "string" } } },
required: ["findings"],
additionalProperties: false
}
})
));
phase("Summarize");
log("Reviews finished");
return await agent(`Summarize these review results, noting failed branches: ${JSON.stringify(findings)}`);
```

Pass `{"areas":["src/server.ts","src/workspaces.ts"]}` in `input.json`:

```bash
devspace workflows run .devspace/workflows/review.js --args-file input.json
devspace workflows wait <run-id> --timeout 30
devspace workflows show <run-id>
devspace workflows save <run-id> --name review --location project
devspace workflows ls --definitions
```

The local CLI registers the current project as a workspace; it must be within configured allowed roots. MCP hosts use the existing `workspace_id`. When enabled, both tool modes expose `run_workflow`, `get_workflow`, `wait_workflow`, `control_workflow`, `list_workflows`, and `save_workflow`. Outer MCP fields use snake_case (`run_id`, `script_path`, `agent_type`, `after_revision`); script options and JSON argument keys retain their original casing.

Optional `workflows.packageRoots` registers directories of packaged workflow definitions. Relative roots resolve from the DevSpace configuration directory; package definitions are addressed as `package:name`. Project definitions take precedence over personal definitions.

The [bundled workflow skill](../skills/workflows/SKILL.md) documents all primitive signatures and lifecycle operations. It is published through workspace skill discovery, or preloaded with `workflows.instructions: "preload"`. The [configuration schema](../schema/v1/devspace.schema.json) contains resource limits and defaults.

## Execution and recovery

Runs execute in the existing local agent daemon and survive host disconnection. SQLite records run state, agent steps, attempts, events, results, and reported usage. Large event histories expose `eventsTruncated` and `nextEventRevision`; pass that cursor as `after_revision` to retrieve the next page. Completed runs export `journal.jsonl` and `result.json` under their workspace transcript directory. Results over 64 KiB return a preview and `resultArtifact`; step details use `outputArtifact`. These paths remain within the owning workspace. JavaScript runs in a bounded QuickJS guest in a worker. It has no Node, filesystem, network, or module-import capability; project access occurs through configured agents. This guest boundary does not sandbox shell commands run by agents.

Use `pause`, `resume`, and `stop` through `control_workflow` or the CLI. Individual `stop_agent` and `restart_agent` operations require a step ID. Pause gates new work and result delivery; it does not interrupt an agent already working. Stop does not undo edits. Provider cancellation must be acknowledged before a replacement turn is allowed; uncertain cancellation requires attention. Headless provider permission requests are recorded and declined; the workflow never manufactures approval.

Replay launches a new run using `resume_from_run_id` (CLI `--resume-from`). Matching recorded results can be reused, and divergence is reported. A replay is not a transaction rollback: an undelivered attempt may already have changed files. After a daemon crash, uncertain attempts are marked for recovery rather than automatically repeating side effects.

`isolation: "worktree"` creates a managed worktree for an agent. Inspect its changes before integration; DevSpace does not automatically merge them. A profile can restrict its write authority with `writeMode: read_only`, and continuation preserves that restriction. Final worktree status includes whether files or commits changed. Worktrees remain under the existing managed-worktree retention and pruning policy.

## Provider behavior

The core workflow protocol stays independent of provider SDKs. Workflow turns disable native delegation where the harness supports it, and DevSpace CLI reentry checks inherited workflow provenance. These are cooperative execution controls: unrestricted shell commands can still launch external programs. ACP has no generic way to disable provider-internal delegation, so its fleet size cannot be strictly bounded beyond the calls DevSpace observes. Adapters translate cancellation, continuation, structured results, progress, and usage. Codex and Claude support native structured-output requests; other adapters use validated JSON text. Validation errors are visible and bounded.

Output-token budgets depend on reported provider usage. They are admission limits: concurrent in-flight requests can overshoot. A provider without reliable usage cannot satisfy a required budget. Missing usage is not treated as zero. Model names, supported effort values, authentication, and provider billing remain specific to each configured harness.

The compatibility target is the portable scripting and lifecycle behavior in the [Claude workflow documentation](https://code.claude.com/docs/en/workflows) and [supplied reference](https://gist.github.com/Waishnav/38801adec1580aa689426ba075490670). Host slash menus, Anthropic account billing, and private cloud infrastructure are outside the local MCP execution layer.

Runnable authoring examples are in [examples/workflows](../examples/workflows): review and verification, a bounded repair loop, isolated migrations, and nested synthesis. Copy definitions into `.devspace/workflows` to discover them by name, and select a configured agent at launch. Nested synthesis expects the review definition to be saved first. Research tasks use the same primitives when the chosen agent actually has search tools.
20 changes: 20 additions & 0 deletions examples/workflows/bounded-repair.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export const meta = {
name: "bounded-repair",
description: "Attempt a repair at most three times, verifying after each attempt"
};
let feedback = args.task;
for (let attempt = 1; attempt <= 3; attempt++) {
phase(`Repair ${attempt}`);
await agent(`Implement the following repair in this workspace: ${feedback}`);
phase(`Verify ${attempt}`);
const result = await agent(`Verify the requested task: ${args.task}. Run the relevant checks.`, {
schema: {
type: "object",
properties: { passed: { type: "boolean" }, feedback: { type: "string" } },
required: ["passed", "feedback"], additionalProperties: false
}
});
if (result?.passed) return result;
feedback = result?.feedback ?? feedback;
}
return { passed: false, feedback };
8 changes: 8 additions & 0 deletions examples/workflows/isolated-migrations.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export const meta = {
name: "isolated-migrations",
description: "Implement independent migration candidates in separate worktrees"
};
return await parallel(args.candidates.map(candidate => () => agent(
`Implement and test this migration candidate: ${candidate}. Report tests and changed paths.`,
{ label: candidate, isolation: "worktree" }
)));
8 changes: 8 additions & 0 deletions examples/workflows/nested-synthesis.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export const meta = {
name: "nested-synthesis",
description: "Run a saved review workflow for each group, then synthesize results"
};
const results = await parallel(args.groups.map(group => () =>
workflow("review-and-verify", { areas: group })
));
return await agent(`Synthesize these verified group reviews, preserving failed coverage: ${JSON.stringify(results)}`);
11 changes: 11 additions & 0 deletions examples/workflows/review-and-verify.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export const meta = {
name: "review-and-verify",
description: "Review independent project areas and verify the combined findings",
phases: [{ title: "Review" }, { title: "Verify" }]
};
phase("Review");
const reviews = await parallel(args.areas.map(area => () =>
agent(`Review ${area}. Return actionable findings with file references. Do not modify files.`, { label: area })
));
phase("Verify");
return await agent(`Verify these findings against the current workspace and report which are confirmed: ${JSON.stringify(reviews)}`);
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@
"start": "node dist/cli.js serve",
"test": "tsx --test --test-concurrency=1 \"src/**/*.test.ts\"",
"test:package-install": "tsx --test --test-concurrency=1 \"test/package-install-smoke.test.ts\"",
"typecheck": "tsc -p tsconfig.json --noEmit"
"typecheck": "tsc -p tsconfig.json --noEmit",
"test:workflows-package": "tsx --test test/workflow-cli-smoke.test.ts"
},
"keywords": [],
"author": "",
Expand All @@ -59,6 +60,7 @@
"@modelcontextprotocol/server": "^2.0.0",
"@opencode-ai/sdk": "1.17.13",
"@pierre/diffs": "^1.3.6",
"ajv": "^8.20.0",
"better-result": "^2.10.0",
"better-sqlite3": "^12.10.0",
"cross-spawn": "^7.0.6",
Expand All @@ -68,9 +70,11 @@
"jsonc-parser": "^3.3.1",
"koffi": "^3.1.2",
"lucide": "^1.24.0",
"quickjs-emscripten": "^0.32.0",
"react": "^19.2.6",
"react-dom": "^19.2.6",
"semver": "^7.8.4",
"typescript": "^6.0.3",
"yaml": "^2.9.0",
"zod": "^4.4.3"
},
Expand All @@ -83,7 +87,6 @@
"@types/semver": "^7.7.1",
"@vitejs/plugin-react": "^6.0.2",
"tsx": "^4.22.3",
"typescript": "^6.0.3",
"vite": "^8.0.14"
},
"overrides": {
Expand Down
64 changes: 61 additions & 3 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading