Skip to content
Closed
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
5 changes: 3 additions & 2 deletions docs/chatgpt-coding-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,9 @@ setup installs in `~/.devspace/skills` when agent tooling is enabled, plus:

When agent tooling is enabled, DevSpace discovers agent profiles from
`~/.devspace/agents/*.md` and project `.devspace/agents/*.md`.
`open_workspace` exposes only usable provider names and profile names with
descriptions. Disabled or unavailable providers and their profiles are omitted.
`open_workspace` exposes only usable provider capability hints and profiles with
their provider and optional model/effort defaults. Disabled or unavailable
providers and their profiles are omitted.

Example profiles are packaged under `examples/agents/` for users who want
starter templates. Copy or adapt them into one of the active profile directories
Expand Down
4 changes: 2 additions & 2 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,8 @@ When agent tooling is enabled, DevSpace discovers agent profiles from:
- `~/.devspace/agents/*.md`
- project `.devspace/agents/*.md`

`open_workspace` returns only usable provider names and profile names with
descriptions. `devspace agents ls` lists existing subagent sessions for the
`open_workspace` returns only usable provider capability hints and profiles with
their provider and optional model/effort defaults. `devspace agents ls` lists existing subagent sessions for the
current workspace, scoped by the workspace environment injected into shell
commands. The `subagents` skill teaches the model to discover targets with
`devspace agents targets`, then use the minimal `devspace agents run`,
Expand Down
30 changes: 24 additions & 6 deletions docs/dynamic-workflows.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,25 @@ When agent tooling is enabled, `open_workspace` stays deliberately small:

```json
{
"agentProviders": ["codex", "claude"],
"agentProviders": [
{
"name": "codex",
"model": { "supported": true, "discovery": "model_dependent" },
"effort": {
"supported": true,
"semantics": "reasoning_effort",
"discovery": "model_dependent"
}
}
],
"agents": [
{ "name": "reviewer", "description": "Review changes and test gaps." }
{
"name": "reviewer",
"description": "Review changes and test gaps.",
"provider": "codex",
"model": "gpt-5.4",
"effort": "high"
}
],
"activeWorkflows": [
{
Expand All @@ -88,7 +104,9 @@ When agent tooling is enabled, `open_workspace` stays deliberately small:
}
```

Provider capability metadata, models, effort semantics, session identifiers,
workflow phases, and internal counters are intentionally absent. Models obtain
execution details only when needed through `devspace agents targets --json` or
the workflow inspection commands.
Provider entries contain compact model and effort capability hints. Profiles
expose only their name, description, provider, and optional model/effort
defaults. Unavailable or unselected providers and profiles are omitted.
Workflow entries keep only the run id, name, live status, and call counters
needed to decide whether to inspect or poll; detailed calls remain available
through the CLI inspection commands.
86 changes: 40 additions & 46 deletions skills/dynamic-workflows/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
---
name: dynamic-workflows
description: Create and run resumable multi-agent orchestration with the DevSpace CLI. Use when work needs programmed fan-out, multiple phases, per-item pipelines, structured aggregation, isolated parallel writers, or recovery after a failed workflow; use a direct subagent for one bounded delegation.
description: Create and run resumable multi-agent workflows with the DevSpace CLI. Use for programmed fan-out, dependent stages, per-item processing, structured aggregation, isolated parallel work, or recovery after a failed run; use a direct subagent for one bounded delegation.
---

# DevSpace Dynamic Workflows
# DevSpace dynamic workflows

Use the DevSpace CLI through the host's shell or process tool. Run commands from the project the workflow should operate on. DevSpace scopes runs to the host workspace when supplied, otherwise to the current Git repository or project directory.

Prefer `--json` from an agent harness: it starts or inspects work without holding one tool call open. Retain the returned workflow id and poll explicitly. Use `--follow` only when streaming output is useful and the shell tool supports a long-running process. Do not combine `--json` and `--follow`.
Use the DevSpace CLI from the project the workflow should operate on. Prefer
JSON output from a coding harness so each command returns promptly and the
harness can poll by id.

## Run and inspect

Expand All @@ -21,13 +21,16 @@ devspace workflow cancel <run-id> --json
devspace workflow ls --json
```

Named workflows live at `.devspace/workflows/<name>.js`. `--script-path` is an alias for `--file`. `--arg key=value` accepts repeated run inputs through the script's `args` value.

Poll `status --json` until the workflow reaches `completed`, `failed`, or `cancelled`. Use `calls` for the compact child-call list and `call` for one call's prompt, result, or error.
Named workflows are project files at `.devspace/workflows/<name>.js`.
`--script-path` is an alias for `--file`; repeat `--arg key=value` to pass
inputs. Poll `status` until the run is `completed`, `failed`, or `cancelled`.
Use `calls` for the compact child-call list and `call` for one call's details.
Use `--follow` instead of `--json` when a long-running shell can stream output.

## Write a workflow

The first executable statement must export literal metadata. The script then uses the provided orchestration primitives and returns a JSON-compatible result.
Export literal metadata, then compose the available primitives. Return a
JSON-compatible value.

```js
export const meta = {
Expand All @@ -52,54 +55,45 @@ const summary = await agent(
return { findings, summary }
```

Available primitives:

- `agent(prompt, options?)` delegates one bounded task. Options are `label`, `phase`, `schema`, `profile`, `provider`, `model`, `effort`, and `isolation: 'worktree'`. `profile` and `provider` are mutually exclusive.
- `parallel([thunks])` runs independent tasks concurrently and preserves input order. A failed branch produces `null` in its slot.
- `pipeline(items, ...stages)` processes each item through dependent stages; failed item chains produce `null` without stopping unrelated items.
- `phase(title)` and `log(message)` record meaningful progress.
- `workflow(nameOrRef, args?)` composes another named workflow or `{ scriptPath }` one level deep.
- `args` contains values passed with `--arg`.
Primitives and options:

Use `devspace agents targets --json` before choosing a profile or provider. Prefer profiles for reusable role instructions and defaults. Only pass model or effort overrides when their exact values are already known.
- `agent(prompt, options?)` delegates one task. Options are `label`, `phase`,
`schema`, `profile`, `provider`, `model`, `effort`, and
`isolation: 'worktree'`. Choose either `profile` or `provider`.
- `parallel([thunks])` runs independent tasks concurrently and keeps input
order. `pipeline(items, ...stages)` runs dependent stages for each item.
- `phase(title)` and `log(message)` record useful progress.
- `workflow(nameOrRef, args?)` composes another workflow one level deep.
- `args` contains values supplied with `--arg`.

Use `schema` when later workflow steps need typed JSON rather than prose:

```js
const review = await agent('Return the discovered bugs.', {
schema: {
type: 'object',
properties: {
bugs: { type: 'array', items: { type: 'string' } },
},
required: ['bugs'],
},
})
```
Use `devspace agents targets --json` before choosing a profile or provider.
Profiles provide reusable role instructions and defaults. Use worktree
isolation for parallel writers that could touch the same files; shared
isolation is suitable for readers or intentionally sequential writers.
Use `schema` when a later stage needs structured JSON.

Use `isolation: 'worktree'` for parallel agents that may modify overlapping checkouts. Shared isolation is appropriate for readers or intentionally sequential writers.
## Common patterns

Workflow scripts must be replayable: do not use `Date.now()`, `Math.random()`, or `new Date()` without an argument. Pass changing values through `args`.
- Fan out correctness, security, and test reviews, then ask one agent to
combine the findings.
- Process a list of files through analysis, implementation, and verification
stages.
- Run competing implementations in isolated worktrees and compare their
results before choosing one.

## Recover a run
## Resume a run

Failed and cancelled runs are terminal. Inspect the prior run, fix or replace its script, then create a resumed run:
Failed or cancelled runs can be resumed after fixing the workflow or its
inputs:

```bash
devspace workflow status <run-id> --json
devspace workflow calls <run-id> --json
devspace workflow call <run-id> <call-index> --json
devspace workflow run --resume <run-id> --json
devspace workflow run --resume <run-id> --file <updated-script> --json
```

Keep completed calls' prompts and options stable when their results should be reused. Resume reuses the unchanged successful prefix and executes from the first call that failed, changed, or cannot be reused.

A completed `isolation: 'worktree'` call cannot be reused because its checkout is not restored. When resume reaches one, that call and every later call execute again, even if their inputs are unchanged. Do not assume mutations from the prior isolated checkout are present in the resumed run.

## Good uses
Inspect `status`, `calls`, and individual `call` results before resuming.
Keep the same profile/provider and prompt for stages whose earlier results
should be reused.

- Fan out a change review across correctness, security, and tests, then synthesize it.
- Analyze many files with the same staged pipeline.
- Run parallel implementations in isolated worktrees and compare their results.
- Encode a repeatable migrate, review, and verify sequence.
Use a direct `devspace agents run` command for one independent delegation.
8 changes: 8 additions & 0 deletions skills/subagents/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ Prefer a configured profile whose description matches the task. Use a provider t

Profiles carry their own provider, instructions, model, and effort defaults. Only pass `--model` or `--effort` when the user supplied an exact value or the value is already known to be valid for that target.

`--model <value>` selects a provider model. `--effort <value>` selects the
provider's reasoning/thinking level (`--thinking` is an alias). Both are
optional; use values reported by `agents targets --json` or a configured
profile.

## Start work

Give the child a self-contained brief. Include the objective, relevant paths, constraints, decisions from the parent conversation, and the expected result. A child cannot see the parent conversation or ask the user for missing context.
Expand All @@ -42,6 +47,9 @@ devspace agents ls --json
- `run <id>` continues the same agent session with a new prompt.
- `ls` returns sessions belonging to the current project.

Use `--json` on every command when the calling harness needs machine-readable
ids, status, responses, or errors.

Poll `show --json` while the status is `starting` or `running`. `idle` means the response is ready; `error` and `stopped` are terminal without a successful response. Use a continuation only when the same context is valuable; start a new subagent for independent work.

## Good uses
Expand Down
5 changes: 4 additions & 1 deletion src/local-agent-resolution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,10 @@ export function resolveLocalAgentExecution(
if (input.profile) {
const profile = input.profiles.find((candidate) => candidate.name === input.profile);
if (!profile) {
const available = input.profiles.map((candidate) => candidate.name).join(", ");
const available = input.profiles
.filter((candidate) => input.availableProviders.includes(candidate.provider))
.map((candidate) => candidate.name)
.join(", ");
throw new LocalAgentResolutionError(
"profile_not_found",
`Unknown agent profile: ${input.profile}${available ? `. Available profiles: ${available}` : ""}`,
Expand Down
2 changes: 2 additions & 0 deletions src/local-agent-targets.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,4 +143,6 @@ assert.throws(

assert.equal(resolveLocalAgentTarget("missing", profiles), undefined);
assert.match(formatAvailableLocalAgentTargets(profiles), /profiles: reviewer, claude/);
assert.match(formatAvailableLocalAgentTargets(profiles, ["codex"]), /profiles: reviewer/);
assert.doesNotMatch(formatAvailableLocalAgentTargets(profiles, ["codex"]), /claude/);
assert.match(formatAvailableLocalAgentTargets([]), /providers: codex, claude, opencode, pi, cursor, copilot/);
5 changes: 4 additions & 1 deletion src/local-agent-targets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,10 @@ export function formatAvailableLocalAgentTargets(
profiles: LocalAgentProfile[],
providers: LocalAgentProvider[] = [...LOCAL_AGENT_PROVIDERS],
): string {
const profileNames = profiles.map((profile) => profile.name);
const availableProviders = new Set(providers);
const profileNames = profiles
.filter((profile) => availableProviders.has(profile.provider))
.map((profile) => profile.name);
const parts = [
profileNames.length > 0 ? `profiles: ${profileNames.join(", ")}` : undefined,
providers.length > 0 ? `providers: ${providers.join(", ")}` : "providers: none",
Expand Down
36 changes: 32 additions & 4 deletions src/open-workspace-capabilities.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,22 @@ const parsed = enabledSchema.parse({
agentsFiles: [],
availableAgentsFiles: [],
skills: [],
agentProviders: ["codex"],
agents: [{ name: "reviewer", description: "Review changes." }],
agentProviders: [{
name: "codex",
model: { supported: true, discovery: "model_dependent" },
effort: {
supported: true,
semantics: "reasoning_effort",
discovery: "model_dependent",
},
}],
agents: [{
name: "reviewer",
description: "Review changes.",
provider: "codex",
model: "gpt-5.4",
effort: "high",
}],
activeWorkflows: [{
id: "wfr_1",
name: "Review",
Expand All @@ -62,8 +76,22 @@ const parsed = enabledSchema.parse({
}],
instruction: "Reuse this workspace.",
});
assert.deepEqual(parsed.agentProviders, ["codex"]);
assert.deepEqual(parsed.agents, [{ name: "reviewer", description: "Review changes." }]);
assert.deepEqual(parsed.agentProviders, [{
name: "codex",
model: { supported: true, discovery: "model_dependent" },
effort: {
supported: true,
semantics: "reasoning_effort",
discovery: "model_dependent",
},
}]);
assert.deepEqual(parsed.agents, [{
name: "reviewer",
description: "Review changes.",
provider: "codex",
model: "gpt-5.4",
effort: "high",
}]);
assert.deepEqual(parsed.activeWorkflows, [{
id: "wfr_1",
name: "Review",
Expand Down
14 changes: 13 additions & 1 deletion src/pi-tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export type ToolResponse<TDetails = unknown> = {
interface ToolContext {
cwd: string;
root: string;
workspaceId?: string;
readRoots?: string[];
}

Expand Down Expand Up @@ -119,7 +120,18 @@ export async function listDirectoryTool(input: LsToolInput, context: ToolContext
}

export async function runShellTool(input: BashToolInput, context: ToolContext): Promise<ToolResponse> {
const tool = createBashTool(context.cwd);
const tool = createBashTool(context.cwd, {
// Keep CLI orchestration launched through MCP attached to the workspace
// that owns this shell call, including non-Git and nested workspaces.
spawnHook: ({ env, ...spawn }) => ({
...spawn,
env: {
...env,
...(context.workspaceId ? { DEVSPACE_WORKSPACE_ID: context.workspaceId } : {}),
DEVSPACE_WORKSPACE_ROOT: context.root,
},
}),
});
const timeout = input.timeout === undefined ? 30 : Math.min(input.timeout, 300);

return runTool((params) => tool.execute("run_shell", params), {
Expand Down
Loading
Loading