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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ devspace doctor

- [Setup Guide](https://github.com/Waishnav/devspace/blob/main/docs/setup.md)
- [ChatGPT Coding Workflow](https://github.com/Waishnav/devspace/blob/main/docs/chatgpt-coding-workflow.md)
- [Persisted Workflows](https://github.com/Waishnav/devspace/blob/main/docs/workflows.md)
- [Configuration Reference](https://github.com/Waishnav/devspace/blob/main/docs/configuration.md)
- [Native File Download](https://github.com/Waishnav/devspace/blob/main/docs/artifact-exchange.md)
- [Security Model](https://github.com/Waishnav/devspace/blob/main/docs/security.md)
Expand Down
3 changes: 3 additions & 0 deletions docs/chatgpt-coding-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,9 @@ managed path `~/.devspace/skills/subagents/SKILL.md`. That copy is refreshed
from the installed DevSpace package and wins over other skills named
`subagents`.

DevSpace also synchronizes `~/.devspace/skills/workflows/SKILL.md` for persisted
multi-agent workflows submitted with `devspace workflow`.

When Subagents are enabled, DevSpace discovers agent profiles
from `~/.devspace/agents/*.md` and project `.devspace/agents/*.md`.
`open_workspace` exposes a compact catalog with profile names, descriptions,
Expand Down
6 changes: 3 additions & 3 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,9 @@ DevSpace discovers standard Agent Skills from `~/.agents/skills`, project
are resolved from the active workspace.

When Subagents are enabled for MCP workspaces, DevSpace keeps its bundled
`subagents` skill synchronized at `~/.devspace/skills/subagents/SKILL.md`.
That managed copy is the authoritative `subagents` skill for DevSpace and is
refreshed when the packaged skill changes.
`subagents` and `workflows` skills synchronized under `~/.devspace/skills/`.
Those managed copies are authoritative for DevSpace and are refreshed when the
packaged skills change.

Subagent providers are explicit. Omitted providers are disabled:

Expand Down
5 changes: 5 additions & 0 deletions docs/local-agent-daemon.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ functionality, but `devspace serve` is not required for local-agent execution.
The daemon is scoped to one DevSpace `stateDir`, so one SQLite store and one
runtime owner serve all clients using that configuration.

Persisted workflows use the same daemon and local agent manager. Active
workflows keep it alive after the submitting CLI exits. Startup reconciles
agent turns before workflow records; shutdown closes workflow supervisors
before the agent manager.

Communication uses a private Unix domain socket on Linux/macOS or a named pipe
on Windows. The endpoint is not exposed through the public MCP HTTP port.
Provider session identifiers and logical agent records are durable; live
Expand Down
4 changes: 2 additions & 2 deletions docs/setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@ These commands do not require `devspace serve`.

This Coding Agent installation is separate from ChatGPT MCP usage. For MCP
workspaces with Subagents enabled, DevSpace manages its own copy at
`~/.devspace/skills/subagents/SKILL.md`; users do not install that copy
manually.
`~/.devspace/skills/subagents/SKILL.md` and its bundled workflow skill at
`~/.devspace/skills/workflows/SKILL.md`; users do not install those copies manually.

### Connect ChatGPT

Expand Down
62 changes: 62 additions & 0 deletions docs/workflows.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Workflows

DevSpace workflows run persisted multi-agent plans through the same on-demand daemon as local subagents. A CLI process submits a run and exits immediately; the daemon remains alive until active workflows finish.

Place reusable workflow scripts in `.devspace/workflows/` and run them by name, or provide a file directly:

```js
export const meta = { name: "review", concurrency: 2 };

const findings = await agent("Review the current changes", {
target: "reviewer",
writeMode: "read_only",
});
return { findings };
```

The script body receives `args` plus the `agent`, `workflow`, `parallel`, `pipeline`, `phase`, and `log` helpers. It must return a JSON value. `agent(prompt, options)` requires a configured profile or provider in `options.target`; `workflow(name, args)` may call one named workflow, with nesting limited to one level.

`parallel(items, worker, concurrency)` returns one outcome per input instead of failing the whole batch when one worker throws. `pipeline(items, ...stages)` passes each raw item through every stage in order; each stage receives `(currentValue, originalItem, index)`. `phase(name, run)` records durable start, completion, and failure events and gives `run` an agent helper that labels its calls with that phase.

Run the script by name or provide a file directly:

```bash
devspace workflow run --name review --args '{"base":"main"}'
devspace workflow run --file ./review.workflow.js --args-file ./inputs.json
```

Exactly one of `--file`, `--name`, or `--resume` is required. `--file` reads the source before contacting the daemon. Named workflows are resolved inside the current workspace. Runs default to `read_only`; `--write-mode allowed` permits workflow calls configured for writes.

`run` returns an asynchronous receipt. Use the returned workflow ID with:

```bash
devspace workflow status <id>
devspace workflow wait <id> --timeout 60
devspace workflow calls <id>
devspace workflow call <id> <index>
devspace workflow events <id> --after <last-sequence> --json
devspace workflow cancel <id>
devspace workflow ls
```

`wait` accepts a timeout from 0 to 60 seconds and returns the latest durable run state on timeout. `calls` returns compact call summaries without prompts, results, or fingerprints. `call` returns the full stored record for one zero-based call index. `events` returns up to 100 persisted log and phase entries after the supplied sequence. All commands accept `--json` for scripts.

Workflow records, calls, and provider continuation state survive CLI exits and daemon restarts. A daemon restart marks unfinished workflows interrupted for explicit inspection or resume; it does not dispatch calls automatically. At startup the daemon first reconciles local agent turns, then workflow runs. During shutdown it closes workflows before the local agent manager so supervisors can stop their child turns cleanly.

Named workflows and their arguments are workspace scoped. IDs from another checkout, workspace, or managed worktree are rejected even when the caller can read the shared DevSpace state directory.

## Bounds

Workflow metadata may set `concurrency` from 1 to 16; the default is 4. The daemon admits at most eight active workflows and eight aggregate workflow agent turns, with at most sixteen script runners including nested scripts. One run may make at most 128 agent turns, including schema-correction turns, and may invoke at most eight nested workflows. Nesting is limited to one level and shares the parent run's admission, cancellation, and call budgets.

Scripts run in QuickJS with a 32 MiB heap, 512 KiB stack, and 15-minute wall clock limit. Source is limited to 64 KiB, arguments to 128 KiB, and workflow or agent results to 256 KiB. Guest `log()` and `phase()` events share a 64 KiB limit per run; bounded internal events such as agent turns and nested workflow source snapshots are additional. These are hard product boundaries rather than tuning controls.

An agent call may include an inline JSON Schema in `options.schema`. DevSpace compiles strict draft-07 structural schemas with at most 16 KiB, 512 nodes, and 16 levels. It rejects `$ref`, `$dynamicRef`, `$recursiveRef`, `$async`, `pattern`, `patternProperties`, and `format`. Invalid output gets one read-only correction in the same agent session; the correction counts toward the 128-turn budget.

## Resume and worktrees

Resume reuses only a matching prefix of completed read-only calls. Any call with write authority, isolation, or a named workflow workspace disables replay for the run. DevSpace also hashes the complete workspace root before the original and resumed runs. More than 20,000 entries, more than 64 MiB of files, any symlink, special file, unreadable path, or other hash failure produces `RECOVERY_CONTEXT_CHANGED`. Large generated trees such as `node_modules` can therefore conservatively disable resume.

An isolated call creates a managed worktree and records it as a workspace. DevSpace retains those worktrees after the run for inspection. Normal stale-worktree cleanup preserves recovery snapshots for commits and dirty tracked changes and skips worktrees with untracked files.

Profiles are resolved in each call’s execution workspace. For isolated worktrees, use global profiles or commit workspace profiles so they exist in the new worktree. A new worktree starts at the source’s committed `HEAD`; uncommitted source changes are not copied.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,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,6 +69,7 @@
"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",
Expand Down
58 changes: 58 additions & 0 deletions pnpm-lock.yaml

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

49 changes: 49 additions & 0 deletions skills/workflows/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
---
name: workflows
description: Run a persisted multi-agent DevSpace workflow from a script file or a named project workflow, then wait for or inspect its result.
---

# DevSpace workflows

Use a workflow when a task has a repeatable multi-agent plan. Keep one-off delegation on the `subagents` commands.

Named workflows live in the current project's `.devspace/workflows/` directory. Run a named workflow or a specific file:

```bash
devspace workflow run --name <name> [--args '<json>']
devspace workflow run --file <path> [--args-file <path>]
```

The command returns a compact `<workflow id="..." status="..."/>` receipt. Keep the ID. Workflow code is read-only by default; add `--write-mode allowed` only when the workflow must modify the project.

Wait up to 60 seconds at a time. Repeat `wait` if the returned status is still running; do not poll `status` in a loop.

```bash
devspace workflow wait <id> --timeout 60
```

Inspect current state, calls, or one full call when needed:

```bash
devspace workflow status <id>
devspace workflow calls <id>
devspace workflow call <id> <index>
devspace workflow events <id> --after <last-sequence>
devspace workflow ls
```

`events` returns at most 100 persisted log and phase entries. Keep the highest sequence and pass it with `--after` for the next page.

Cancel only when the parent task no longer needs the run:

```bash
devspace workflow cancel <id>
```

Resume an interrupted or failed run from its durable call history:

```bash
devspace workflow run --resume <id> [--args '<json>']
```

Use `--json` only for scripts that need structured output.
27 changes: 27 additions & 0 deletions src/cli-workspace.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,33 @@ try {
workspaceRoot: resolve(nestedRoot),
});

let managedChecks = 0;
assert.deepEqual(resolveCliWorkspaceContext([repositoryRoot], {
DEVSPACE_WORKSPACE_ID: "ws_managed",
DEVSPACE_WORKSPACE_ROOT: plainRoot,
}, nestedRoot, (workspaceRoot, workspaceId) => {
managedChecks += 1;
return workspaceId === "ws_managed" && workspaceRoot === plainRoot;
}), {
workspaceId: "ws_managed",
workspaceRoot: plainRoot,
});
assert.equal(managedChecks, 1);

resolveCliWorkspaceContext([repositoryRoot], {
DEVSPACE_WORKSPACE_ROOT: plainRoot,
}, nestedRoot, () => {
throw new Error("managed authorization must require an explicit workspace id");
});

assert.throws(
() => resolveCliWorkspaceContext([repositoryRoot], {
DEVSPACE_WORKSPACE_ID: "ws_wrong",
DEVSPACE_WORKSPACE_ROOT: plainRoot,
}, nestedRoot, () => false),
/outside allowed roots/,
);

if (process.platform !== "win32") {
const repositoryAlias = join(root, "repository-alias");
symlinkSync(repositoryRoot, repositoryAlias, "dir");
Expand Down
17 changes: 12 additions & 5 deletions src/cli-workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export function resolveCliWorkspaceContext(
allowedRoots: readonly string[],
env: NodeJS.ProcessEnv = process.env,
cwd = process.cwd(),
authorizeManagedWorkspace?: (workspaceRoot: string, workspaceId: string) => boolean,
): CliWorkspaceContext {
const workspaceId = env.DEVSPACE_WORKSPACE_ID?.trim() || undefined;
const injectedRoot = workspaceId ? env.DEVSPACE_WORKSPACE_ROOT?.trim() : undefined;
Expand All @@ -21,11 +22,17 @@ export function resolveCliWorkspaceContext(
);

if (!workspaceId) return { workspaceId, workspaceRoot: candidate };

return {
workspaceId,
workspaceRoot: assertAllowedPath(candidate, allowedRoots.map(canonicalizePath)),
};
try {
return {
workspaceId,
workspaceRoot: assertAllowedPath(candidate, allowedRoots.map(canonicalizePath)),
};
} catch (error) {
if (authorizeManagedWorkspace?.(candidate, workspaceId)) {
return { workspaceId, workspaceRoot: candidate };
}
throw error;
}
}

function canonicalizePath(path: string): string {
Expand Down
Loading
Loading