diff --git a/docs/agent-profile-schema.md b/docs/agent-profile-schema.md new file mode 100644 index 0000000..2070916 --- /dev/null +++ b/docs/agent-profile-schema.md @@ -0,0 +1,342 @@ +# Local agent profile schema + +DevSpace local agent profiles are user-owned markdown files with YAML front matter. +They describe how a local coding-agent CLI can be used as a worker under ChatGPT +supervision. + +The packaged files in `examples/agents/` are starter templates only. DevSpace does +not currently parse, load, activate, or run local agent profile definitions. A +future profile loader can define the user-owned directory for active profiles. + +## Minimal shape + +```md +--- +schema: devspace-agent/v1 +name: codex-explorer +description: Read-only Codex agent for bounded codebase questions. +provider: codex +backend: cli + +capabilities: + read: true + write: false + shell: false + background: true + resume: true + +workspace: + default: current + isolation: none + writeMode: read_only + +actions: + start: + command: codex + args: + - exec + - --json + - -C + - "{workspace}" + - "{prompt}" + background: true + output: jsonl + + followup: + strategy: resume_command + command: codex + args: + - exec + - resume + - "{externalSessionId}" + - --json + - "{prompt}" + + read: + strategy: devspace_process_poll + + cancel: + strategy: devspace_process_signal + signal: SIGINT + + diff: + strategy: none + +safety: + requireExplicitUserIntent: true + allowWrites: false + requireReviewBeforeFinal: true +--- + +Use this agent for bounded read-only codebase investigation. +``` + +## Front matter fields + +### `schema` + +Required schema identifier. + +Current value: + +```yaml +schema: devspace-agent/v1 +``` + +### `name` + +Stable profile identifier shown to the model and user. + +Use lowercase kebab-case names, for example: + +```yaml +name: codex-explorer +``` + +### `description` + +Short human-readable purpose. This should help the supervising model decide when +the agent is appropriate. + +### `provider` + +The local agent family or CLI provider. + +Examples: + +```yaml +provider: codex +provider: claude +provider: opencode +provider: cursor +provider: pi +provider: copilot +``` + +### `backend` + +Execution backend. The near-term templates use CLI-backed agents: + +```yaml +backend: cli +``` + +Future profiles may support protocol-backed backends such as ACP without changing +the high-level profile name. + +## Capabilities + +Capabilities describe what the worker is allowed or expected to do. + +```yaml +capabilities: + read: true + write: false + shell: false + background: true + resume: true +``` + +- `read`: the agent can inspect project files. +- `write`: the agent may modify files. +- `shell`: the agent may run shell commands. +- `background`: the agent can be started as a long-running process. +- `resume`: the agent supports follow-up prompts against an existing session. + +These fields are descriptive in the current template-only stage. A future parser +should validate them before rendering an available-agent catalog or exposing +runtime tools. + +## Workspace policy + +```yaml +workspace: + default: current + isolation: user_decides + writeMode: allowed +``` + +- `default`: default workspace source. Current templates use `current`. +- `isolation`: whether to use the same checkout, a branch, a worktree, or let the + user decide. +- `writeMode`: whether writes are allowed. + +Recommended values: + +```yaml +isolation: none +isolation: user_decides + +writeMode: read_only +writeMode: allowed +``` + +Use read-only profiles for review, lookup, and second opinions. Use write-capable +profiles only when the user explicitly asks a local agent to implement or edit. + +## Actions + +Actions define lifecycle commands and strategies. + +### `actions.start` + +Starts the local agent. + +```yaml +actions: + start: + command: codex + args: + - exec + - --json + - -C + - "{workspace}" + - "{prompt}" + background: true + output: jsonl +``` + +Use `command` plus `args` arrays. Do not use free-form shell strings. + +Good: + +```yaml +command: codex +args: + - exec + - --json + - -C + - "{workspace}" + - "{prompt}" +``` + +Avoid: + +```yaml +run: "codex exec --json -C {workspace} {prompt}" +``` + +Argv arrays are easier to validate, escape, log, review, and migrate to future +backends. + +### `actions.followup` + +Defines how to continue a previous worker session. + +Supported strategy names used by the starter templates: + +```yaml +strategy: resume_command +strategy: fresh_prompt_with_context +``` + +Use `resume_command` when the provider has an explicit resume/session flag. Use +`fresh_prompt_with_context` when follow-up work must include previous summaries, +review findings, and diff context in a new prompt. + +### `actions.read` + +Defines how DevSpace should read worker output. + +The starter templates use: + +```yaml +read: + strategy: devspace_process_poll +``` + +### `actions.cancel` + +Defines how DevSpace should interrupt a running worker. + +The starter templates use: + +```yaml +cancel: + strategy: devspace_process_signal + signal: SIGINT +``` + +Prefer interruption over deleting provider session history. + +### `actions.diff` + +Defines how DevSpace can inspect worker file changes. + +Read-only profiles should use: + +```yaml +diff: + strategy: none +``` + +Write-capable profiles should use: + +```yaml +diff: + strategy: git_diff +``` + +## Placeholders + +The examples use placeholders that a future runtime can substitute safely: + +```text +{workspace} +{prompt} +{externalSessionId} +``` + +- `{workspace}`: absolute workspace path selected by DevSpace or the user. +- `{prompt}`: focused worker prompt created by the supervising model. +- `{externalSessionId}`: provider session id returned by a previous agent run. + +## Safety policy + +```yaml +safety: + requireExplicitUserIntent: true + allowWrites: false + requireReviewBeforeFinal: true +``` + +Recommended write-capable profile safety: + +```yaml +safety: + requireExplicitUserIntent: true + allowWrites: true + requireDiffReview: true + requireTestsOrExplanation: true +``` + +Profiles should make user intent and review requirements explicit. DevSpace should +not silently delegate work to local agents, and the supervising model should not +present worker output as verified until it has reviewed the result. + +## Markdown body + +The markdown body should explain when to use the agent and provide a worker prompt +template. + +Recommended sections: + +- `Use this agent when ...` +- `Good tasks:` +- `Worker prompt style:` +- A final report format that the supervising model can review. + +The body is model-facing guidance. Keep it practical and concise. + +## Current non-goals + +The current examples do not add: + +- local agent profile parsing. +- automatic activation of packaged examples. +- `devspace agents init`. +- generated available-agent catalogs. +- first-class agent runtime tools. +- ACP-backed execution. + +Those can be added in later PRs without changing the template intent. diff --git a/docs/chatgpt-coding-workflow.md b/docs/chatgpt-coding-workflow.md index 13269f5..2a73433 100644 --- a/docs/chatgpt-coding-workflow.md +++ b/docs/chatgpt-coding-workflow.md @@ -83,12 +83,18 @@ DevSpace discovers standard Agent Skills from: - `~/.agents/skills` - project `.agents/skills` +- `~/.devspace/skills` It also keeps compatibility with: +- the bundled `local-agent-delegation` skill when `DEVSPACE_LOCAL_AGENTS=1`, unless `~/.devspace/skills/local-agent-delegation/SKILL.md` exists - `DEVSPACE_AGENT_DIR/skills`, defaulting to `~/.codex/skills` - additional paths from `DEVSPACE_SKILL_PATHS` +Example local coding-agent profiles are packaged under `examples/agents/` for +users who want starter templates. These examples are inert: DevSpace does not +currently parse, load, activate, or run local agent profile definitions. + Legacy project paths such as `.pi/skills` can be added through `DEVSPACE_SKILL_PATHS` when needed. When `open_workspace` returns matching skills, the model should read the @@ -99,7 +105,9 @@ Skill paths may be outside the workspace. DevSpace only permits reading: - advertised `SKILL.md` files - files under a skill directory after that skill's `SKILL.md` has been read -Set `DEVSPACE_SKILLS=0` to hide skills from workspace output. +Set `DEVSPACE_SKILLS=0` to hide skills from workspace output. Set +`DEVSPACE_LOCAL_AGENTS=1` to expose the experimental `local-agent-delegation` +skill. ## Tool Names diff --git a/docs/configuration.md b/docs/configuration.md index 3229363..29cf116 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -99,6 +99,7 @@ sessions. | Variable | Purpose | | --- | --- | | `DEVSPACE_SKILLS` | Set to `0` to hide skills. Enabled by default. | +| `DEVSPACE_LOCAL_AGENTS` | Set to `1` to expose the local-agent delegation skill. Experimental and disabled by default. | | `DEVSPACE_AGENT_DIR` | Defaults to `~/.codex`; its `skills` child is loaded for compatibility. | | `DEVSPACE_SKILL_PATHS` | Optional comma-separated additional skill directories. | @@ -106,12 +107,18 @@ DevSpace discovers standard Agent Skills from: - `~/.agents/skills` - project `.agents/skills` +- `~/.devspace/skills` It also keeps compatibility with: +- the bundled `local-agent-delegation` skill when `DEVSPACE_LOCAL_AGENTS=1`, unless `~/.devspace/skills/local-agent-delegation/SKILL.md` exists - `DEVSPACE_AGENT_DIR/skills`, defaulting to `~/.codex/skills` - additional paths from `DEVSPACE_SKILL_PATHS` +Starter local coding-agent profile templates are available under +`examples/agents/`. These files are inert examples: DevSpace does not currently +parse, load, activate, or run local agent profile definitions. + Legacy project paths such as `.pi/skills` can be added through `DEVSPACE_SKILL_PATHS` when needed. Example: diff --git a/docs/gotchas.md b/docs/gotchas.md index c6ef1d2..e6874d1 100644 --- a/docs/gotchas.md +++ b/docs/gotchas.md @@ -197,12 +197,18 @@ DevSpace looks in standard Agent Skills locations: - `~/.agents/skills` - project `.agents/skills` +- `~/.devspace/skills` It also checks compatibility and custom paths: +- the bundled `local-agent-delegation` skill when `DEVSPACE_LOCAL_AGENTS=1`, unless `~/.devspace/skills/local-agent-delegation/SKILL.md` exists - `DEVSPACE_AGENT_DIR/skills`, defaulting to `~/.codex/skills` - additional paths from `DEVSPACE_SKILL_PATHS` +Packaged local-agent examples under `examples/agents/` are inert templates only. +DevSpace does not currently parse, load, activate, or run local agent profile +definitions. + Legacy project paths such as `.pi/skills` can be added through `DEVSPACE_SKILL_PATHS` when needed. If a skill appears in `open_workspace`, the model must read that skill's diff --git a/examples/agents/claude-implementer.md b/examples/agents/claude-implementer.md new file mode 100644 index 0000000..235d7e5 --- /dev/null +++ b/examples/agents/claude-implementer.md @@ -0,0 +1,108 @@ +--- +schema: devspace-agent/v1 +name: claude-implementer +description: Claude Code implementation worker for larger edits, refactors, and follow-up loops. +provider: claude +backend: cli + +capabilities: + read: true + write: true + shell: true + background: true + resume: true + +workspace: + default: current + isolation: user_decides + writeMode: allowed + +actions: + start: + command: claude + args: + - -p + - --output-format + - stream-json + - --verbose + - "{prompt}" + background: true + output: stream-json + + followup: + strategy: resume_command + command: claude + args: + - --resume + - "{externalSessionId}" + - -p + - --output-format + - stream-json + - --verbose + - "{prompt}" + + list: + command: claude + args: + - agents + - --json + + read: + strategy: devspace_process_poll + + cancel: + strategy: devspace_process_signal + signal: SIGINT + + diff: + strategy: git_diff + +safety: + requireExplicitUserIntent: true + allowWrites: true + requireDiffReview: true + requireTestsOrExplanation: true +--- + +Use this agent when the task benefits from a stronger implementation worker. + +Good tasks: + +- Multi-file implementation. +- Refactor with clear boundaries. +- Test repair loop. +- Apply detailed review comments. +- Continue an already-started implementation. + +Worker prompt style: + +```text +You are a local Claude Code implementation worker under ChatGPT supervision. + +Goal: + + +Context: + + +Plan: + + +Constraints: + + +Rules: +- Keep changes focused. +- Do not rewrite unrelated code. +- Preserve public behavior unless asked. +- Run or explain relevant tests. +- Return a concise final report. + +Final report format: +summary: +files_changed: +tests_run: +risks: +blockers: +follow_up_needed: +``` diff --git a/examples/agents/codex-explorer.md b/examples/agents/codex-explorer.md new file mode 100644 index 0000000..5ca2212 --- /dev/null +++ b/examples/agents/codex-explorer.md @@ -0,0 +1,91 @@ +--- +schema: devspace-agent/v1 +name: codex-explorer +description: Read-only Codex agent for bounded codebase questions and architecture exploration. +provider: codex +backend: cli + +capabilities: + read: true + write: false + shell: false + background: true + resume: true + +workspace: + default: current + isolation: none + writeMode: read_only + +actions: + start: + command: codex + args: + - exec + - --json + - -C + - "{workspace}" + - "{prompt}" + background: true + output: jsonl + + followup: + strategy: resume_command + command: codex + args: + - exec + - resume + - "{externalSessionId}" + - --json + - "{prompt}" + + read: + strategy: devspace_process_poll + + cancel: + strategy: devspace_process_signal + signal: SIGINT + + diff: + strategy: none + +safety: + requireExplicitUserIntent: true + allowWrites: false + requireReviewBeforeFinal: true +--- + +Use this agent when the user wants a bounded read-only investigation, second opinion, +or explanation of a code path. + +Good tasks: + +- Find where a feature is implemented. +- Explain an architecture boundary. +- Review a module without changing files. +- Identify likely files for a future change. + +Worker prompt style: + +```text +You are a read-only codebase explorer. + +Question: + + +Scope: + + +Rules: +- Do not modify files. +- Cite file paths and symbols. +- Separate facts from guesses. +- Keep the answer concise. + +Final report format: +answer: +evidence: +relevant_files: +confidence: +unknowns: +``` diff --git a/examples/agents/codex-worker.md b/examples/agents/codex-worker.md new file mode 100644 index 0000000..00d89b0 --- /dev/null +++ b/examples/agents/codex-worker.md @@ -0,0 +1,102 @@ +--- +schema: devspace-agent/v1 +name: codex-worker +description: Codex implementation worker for focused, user-approved coding tasks. +provider: codex +backend: cli + +capabilities: + read: true + write: true + shell: true + background: true + resume: true + +workspace: + default: current + isolation: user_decides + writeMode: allowed + +actions: + start: + command: codex + args: + - exec + - --json + - -C + - "{workspace}" + - "{prompt}" + background: true + output: jsonl + + followup: + strategy: resume_command + command: codex + args: + - exec + - resume + - "{externalSessionId}" + - --json + - "{prompt}" + + read: + strategy: devspace_process_poll + + cancel: + strategy: devspace_process_signal + signal: SIGINT + + diff: + strategy: git_diff + +safety: + requireExplicitUserIntent: true + allowWrites: true + requireDiffReview: true + requireTestsOrExplanation: true +--- + +Use this agent when ChatGPT has already planned a focused implementation and the +user wants Codex to execute it locally. + +Good tasks: + +- Implement a small feature from a clear plan. +- Fix a bug with known reproduction steps. +- Add tests for an existing code path. +- Apply review comments. + +Worker prompt style: + +```text +You are a local implementation worker under ChatGPT supervision. + +Goal: + + +Plan: + + +Constraints: + + +Files to focus: + + +Tests to run: + + +Rules: +- Keep changes focused. +- Follow existing project style. +- Do not perform unrelated refactors. +- Do not hide failures. +- Return a final report. + +Final report format: +summary: +files_changed: +tests_run: +blockers: +follow_up_needed: +``` diff --git a/examples/agents/copilot-reviewer.md b/examples/agents/copilot-reviewer.md new file mode 100644 index 0000000..640b405 --- /dev/null +++ b/examples/agents/copilot-reviewer.md @@ -0,0 +1,96 @@ +--- +schema: devspace-agent/v1 +name: copilot-reviewer +description: GitHub Copilot CLI reviewer for read-only code questions and review passes. +provider: copilot +backend: cli + +capabilities: + read: true + write: false + shell: false + background: true + resume: true + +workspace: + default: current + isolation: none + writeMode: read_only + +actions: + start: + command: copilot + args: + - --model + - auto + - -p + - "{prompt}" + - --output-format + - json + background: true + output: jsonl + + followup: + strategy: resume_command + command: copilot + args: + - --model + - auto + - -p + - "{prompt}" + - --resume + - "{externalSessionId}" + - --output-format + - json + + read: + strategy: devspace_process_poll + + cancel: + strategy: devspace_process_signal + signal: SIGINT + + diff: + strategy: none + +safety: + requireExplicitUserIntent: true + allowWrites: false + requireReviewBeforeFinal: true +--- + +Use this agent when the user wants a GitHub Copilot-powered second opinion, +review, or codebase answer. + +Good tasks: + +- Review changed files. +- Find likely bug sources. +- Explain repository structure. +- Suggest tests. + +Worker prompt style: + +```text +You are a read-only Copilot reviewer under ChatGPT supervision. + +Question: + + +Scope: + + +Rules: +- Do not modify files. +- Cite exact files and symbols. +- Return concise findings. +- Separate facts from guesses. + +Final report format: +answer: +findings: +evidence: +relevant_files: +confidence: +unknowns: +``` diff --git a/examples/agents/cursor-agent-worker.md b/examples/agents/cursor-agent-worker.md new file mode 100644 index 0000000..89be2b1 --- /dev/null +++ b/examples/agents/cursor-agent-worker.md @@ -0,0 +1,91 @@ +--- +schema: devspace-agent/v1 +name: cursor-agent-worker +description: Cursor Agent worker for fast implementation or review using local Cursor CLI. +provider: cursor +backend: cli + +capabilities: + read: true + write: true + shell: true + background: true + resume: false + +workspace: + default: current + isolation: user_decides + writeMode: allowed + +actions: + start: + command: cursor-agent + args: + - -p + - --output-format + - stream-json + - --trust + - "{prompt}" + background: true + output: stream-json + + followup: + strategy: fresh_prompt_with_context + + read: + strategy: devspace_process_poll + + cancel: + strategy: devspace_process_signal + signal: SIGINT + + diff: + strategy: git_diff + +safety: + requireExplicitUserIntent: true + allowWrites: true + requireDiffReview: true + requireTestsOrExplanation: true +--- + +Use this agent when the user wants Cursor's local agent/model to quickly execute +or inspect a task. + +Good tasks: + +- Fast implementation pass. +- UI/UX-oriented code review. +- Alternative implementation idea. +- Lightweight refactor. + +Worker prompt style: + +```text +You are a local Cursor Agent worker under ChatGPT supervision. + +Goal: + + +Context: + + +Plan: + + +Rules: +- Keep changes focused. +- Do not make unrelated edits. +- Preserve existing style. +- Report tests and blockers. + +Final report format: +summary: +files_changed: +tests_run: +blockers: +follow_up_needed: +``` + +Because this profile uses `fresh_prompt_with_context`, include the previous worker +summary and review findings when sending follow-up work. diff --git a/examples/agents/opencode-explorer.md b/examples/agents/opencode-explorer.md new file mode 100644 index 0000000..9a86ce6 --- /dev/null +++ b/examples/agents/opencode-explorer.md @@ -0,0 +1,94 @@ +--- +schema: devspace-agent/v1 +name: opencode-explorer +description: OpenCode read-only explorer for fast codebase lookup and bounded questions. +provider: opencode +backend: cli + +capabilities: + read: true + write: false + shell: false + background: true + resume: true + +workspace: + default: current + isolation: none + writeMode: read_only + +actions: + start: + command: opencode + args: + - run + - --format + - json + - --dir + - "{workspace}" + - "{prompt}" + background: true + output: jsonl + + followup: + strategy: resume_command + command: opencode + args: + - run + - --format + - json + - --session + - "{externalSessionId}" + - --dir + - "{workspace}" + - "{prompt}" + + read: + strategy: devspace_process_poll + + cancel: + strategy: devspace_process_signal + signal: SIGINT + + diff: + strategy: none + +safety: + requireExplicitUserIntent: true + allowWrites: false + requireReviewBeforeFinal: true +--- + +Use this agent for fast, read-only codebase exploration. + +Good tasks: + +- Find relevant files. +- Explain a subsystem. +- Identify test coverage gaps. +- Compare possible implementation locations. + +Worker prompt style: + +```text +You are a read-only OpenCode explorer. + +Question: + + +Scope: + + +Rules: +- Do not modify files. +- Cite exact file paths. +- Prefer concise findings. +- State uncertainty. + +Final report format: +answer: +evidence: +relevant_files: +confidence: +unknowns: +``` diff --git a/examples/agents/pi-reviewer.md b/examples/agents/pi-reviewer.md new file mode 100644 index 0000000..21e842e --- /dev/null +++ b/examples/agents/pi-reviewer.md @@ -0,0 +1,90 @@ +--- +schema: devspace-agent/v1 +name: pi-reviewer +description: Pi read-only reviewer for quick code review and targeted questions. +provider: pi +backend: cli + +capabilities: + read: true + write: false + shell: false + background: true + resume: true + +workspace: + default: current + isolation: none + writeMode: read_only + +actions: + start: + command: pi + args: + - -p + - --mode + - json + - "{prompt}" + background: true + output: jsonl + + followup: + strategy: resume_command + command: pi + args: + - -p + - --mode + - json + - --session-id + - "{externalSessionId}" + - "{prompt}" + + read: + strategy: devspace_process_poll + + cancel: + strategy: devspace_process_signal + signal: SIGINT + + diff: + strategy: none + +safety: + requireExplicitUserIntent: true + allowWrites: false + requireReviewBeforeFinal: true +--- + +Use this agent for lightweight review and targeted read-only investigation. + +Good tasks: + +- Review a diff. +- Find possible bugs. +- Explain a small subsystem. +- Check whether tests cover an edge case. + +Worker prompt style: + +```text +You are a read-only local code reviewer. + +Question: + + +Scope: + + +Rules: +- Do not modify files. +- Cite evidence. +- Focus on actionable findings. +- Avoid broad rewrites. + +Final report format: +findings: +evidence: +risk_level: +recommended_next_steps: +unknowns: +``` diff --git a/package.json b/package.json index aabd492..4a8d605 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,9 @@ "files": [ "dist", "docs", + "examples", "scripts", + "skills", "README.md" ], "publishConfig": { diff --git a/skills/local-agent-delegation/SKILL.md b/skills/local-agent-delegation/SKILL.md new file mode 100644 index 0000000..5af2f9a --- /dev/null +++ b/skills/local-agent-delegation/SKILL.md @@ -0,0 +1,177 @@ +--- +name: local-agent-delegation +description: Delegate coding tasks to user-configured local coding agents such as Codex, Claude Code, OpenCode, Cursor Agent, Pi, or Copilot CLI. +--- + +# Local Agent Delegation + +Use this skill when the user explicitly asks to delegate work to another coding agent, use a named local agent, get a second opinion, compare implementations, run agents in parallel, or create a subagent-like workflow. + +Do not use local agents silently. Tell the user when another local agent is being used. + +## Core idea + +You are the supervisor. A local coding agent is the worker. + +Your responsibilities are: + +1. Understand the user's goal. +2. Decide whether delegation is useful. +3. Choose the right local agent or CLI. +4. Give the worker a focused prompt. +5. Inspect the result yourself. +6. Review diffs, tests, and risks before telling the user the work is done. + +## When to delegate + +Good delegation requests include: + +- "Ask another agent to look at this." +- "Have Claude/Codex/OpenCode/Pi implement this." +- "Run this in the background." +- "Compare two approaches." +- "Use a local subagent for this." +- "Get a second opinion on the architecture/test gaps/security risk." + +Do not delegate just because the task is coding-related. Use the normal DevSpace tools directly unless the user asks for delegation, another agent's opinion, parallel work, or a named local coding agent. + +## CLI execution guidance + +Prefer structured non-interactive CLI modes when available. + +Examples of useful patterns: + +```bash +codex exec --json -C "$WORKSPACE" "$PROMPT" +claude -p --output-format stream-json "$PROMPT" +opencode run --format json --dir "$WORKSPACE" "$PROMPT" +cursor-agent -p --output-format stream-json "$PROMPT" +pi -p --mode json "$PROMPT" +copilot -p "$PROMPT" --output-format json +``` + +These examples are illustrative. Real implementations should pass workspace and prompt values as separate argv entries without a shell, not concatenate untrusted prompt text into a shell command string. + +Use exact command templates from user-provided instructions when they are available. Do not invent provider-specific flags when the user has already supplied a command shape. + +Packaged files under `examples/agents/` are templates only. DevSpace does not currently parse, load, activate, or run local agent profile definitions. + +If no command shape exists for a requested agent, use the installed CLI's help output only when needed, then summarize what you found before running it. + +## Background execution + +When DevSpace exposes long-running process tools, prefer background execution for long tasks. + +Start the local agent process, keep the returned process/session id, and poll output later. + +Use this pattern for long implementations, test repair loops, large reviews, multi-step investigations, and agents that stream JSON or progress logs. + +When polling output, summarize useful progress instead of forwarding noisy terminal logs. + +If the worker is clearly stuck, running the wrong task, or burning resources, interrupt the current process or turn. Do not delete provider session history unless the user explicitly asks. + +## Follow-up prompts + +When sending a follow-up to the same local agent, include: + +```text +previous_task: +current_status: +review_findings: +requested_changes: +success_criteria: +``` + +Prefer the agent profile's resume/session mechanism when available. + +If resume is not available, include the previous worker summary and relevant diff context in a fresh prompt. + +## Worker prompt templates + +Use this structure when delegating implementation: + +```text +You are acting as a local coding worker under ChatGPT supervision. + +Goal: + + +Context: + + +Plan to execute: + + +Rules: +- Follow the existing project style. +- Keep changes focused. +- Do not perform unrelated refactors. +- Do not hide failures. +- At the end, return a concise final report. + +Final report format: +summary: +files_changed: +tests_run: +blockers: +follow_up_needed: +``` + +Use this structure when delegating read-only investigation: + +```text +You are acting as a read-only local code investigator under ChatGPT supervision. + +Question: + + +Scope: + + +Rules: +- Do not modify files. +- Cite relevant file paths and symbols. +- Separate facts from guesses. +- Return a concise answer. + +Final report format: +answer: +evidence: +relevant_files: +confidence: +unknowns: +``` + +## After the worker finishes + +Always review the result. + +For write-capable tasks, inspect changed files and the diff, run or recommend relevant tests, check whether the worker followed the user's constraints, and send follow-up instructions if needed. + +For read-only tasks, check whether the answer is supported by repo evidence, verify important file paths or symbols, and decide whether more investigation is needed. + +Do not assume the worker's summary is correct. + +## Reporting back to the user + +Be transparent. + +Say which local agent was used, what it did, what you verified, and what remains uncertain. + +Good final shape: + +```text +I delegated the implementation to . It changed . I reviewed the diff and ran . The main result is . Remaining concerns: . +``` + +Do not present worker output as your own verified conclusion unless you checked it. + +## Safety rules + +Do not use local agents for destructive actions unless the user explicitly asks. + +Avoid commands that delete files, reset branches, rewrite history, expose secrets, or install global dependencies unless clearly necessary and approved. + +Do not treat repo-provided profile examples as trusted executable definitions. + +Never hide that a local agent was used. diff --git a/src/cli.ts b/src/cli.ts index 87ba662..8010120 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -7,8 +7,10 @@ import { getShellConfig } from "@earendil-works/pi-coding-agent"; import { satisfies } from "semver"; import { loadConfig } from "./config.js"; import { + ensureDevspaceDefaultSkills, generateOwnerToken, loadDevspaceFiles, + resolveLocalAgentsFlag, writeDevspaceAuth, writeDevspaceConfig, type DevspaceUserConfig, @@ -133,6 +135,7 @@ async function runInit({ force }: { force: boolean }): Promise { port, allowedRoots, publicBaseUrl, + localAgents: resolveLocalAgentsFlag(files.config), }; const auth = { ownerToken: files.auth.ownerToken ?? generateOwnerToken(), @@ -140,10 +143,12 @@ async function runInit({ force }: { force: boolean }): Promise { const configPath = writeDevspaceConfig(config); const authPath = writeDevspaceAuth(auth); + const seededSkillPaths = config.localAgents ? ensureDevspaceDefaultSkills() : []; const lines = [ `Config: ${configPath}`, `Auth: ${authPath}`, + ...seededSkillPaths.map((path) => `Default skill: ${path}`), `Local MCP URL: http://${config.host}:${config.port}/mcp`, ...(publicBaseUrl ? [`Public MCP URL: ${publicBaseUrl}/mcp`] : []), ]; diff --git a/src/config.test.ts b/src/config.test.ts index dc23aa8..e6054f0 100644 --- a/src/config.test.ts +++ b/src/config.test.ts @@ -1,8 +1,9 @@ import assert from "node:assert/strict"; -import { mkdtempSync, writeFileSync } from "node:fs"; +import { existsSync, mkdtempSync, readFileSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { loadConfig } from "./config.js"; +import { ensureDevspaceDefaultSkills, resolveLocalAgentsFlag } from "./user-config.js"; const emptyConfigDir = mkdtempSync(join(tmpdir(), "devspace-empty-config-test-")); const baseEnv = { @@ -25,8 +26,25 @@ assert.equal(loadConfig({ ...baseEnv, DEVSPACE_TOOL_MODE: "codex" }).toolMode, " assert.equal(loadConfig({ ...baseEnv, DEVSPACE_MINIMAL_TOOLS: "0" }).toolMode, "full"); assert.equal(loadConfig({ ...baseEnv, DEVSPACE_MINIMAL_TOOLS: "1" }).toolMode, "minimal"); assert.equal(loadConfig(baseEnv).skillsEnabled, true); +assert.equal(loadConfig(baseEnv).devspaceSkillsDir, join(emptyConfigDir, "skills")); +assert.equal(loadConfig(baseEnv).localAgents, false); assert.equal(loadConfig({ ...baseEnv, DEVSPACE_SKILLS: "0" }).skillsEnabled, false); assert.equal(loadConfig({ ...baseEnv, DEVSPACE_SKILLS: "1" }).skillsEnabled, true); +assert.equal( + loadConfig({ ...baseEnv, DEVSPACE_LOCAL_AGENTS: "1" }).localAgents, + true, +); +assert.equal(resolveLocalAgentsFlag({}, {}), undefined); +assert.equal(resolveLocalAgentsFlag({ localAgents: true }, {}), true); +assert.equal(resolveLocalAgentsFlag({ localAgents: true }, { DEVSPACE_LOCAL_AGENTS: "0" }), false); +assert.equal(resolveLocalAgentsFlag({}, { DEVSPACE_LOCAL_AGENTS: "1" }), true); + +const seededConfigDir = mkdtempSync(join(tmpdir(), "devspace-seeded-skills-test-")); +const seededSkillPaths = ensureDevspaceDefaultSkills({ DEVSPACE_CONFIG_DIR: seededConfigDir }); +assert.deepEqual(seededSkillPaths, [join(seededConfigDir, "skills", "local-agent-delegation", "SKILL.md")]); +assert.equal(existsSync(seededSkillPaths[0]), true); +assert.match(readFileSync(seededSkillPaths[0], "utf8"), /name: local-agent-delegation/); +assert.deepEqual(ensureDevspaceDefaultSkills({ DEVSPACE_CONFIG_DIR: seededConfigDir }), []); assert.throws( () => loadConfig({ ...baseEnv, DEVSPACE_WIDGETS: "invalid" }), @@ -150,6 +168,7 @@ writeFileSync( port: 8787, allowedRoots: [process.cwd()], publicBaseUrl: "https://devspace.example.com", + localAgents: true, }), ); writeFileSync( @@ -163,6 +182,7 @@ const fileConfig = loadConfig({ DEVSPACE_CONFIG_DIR: configDir }); assert.equal(fileConfig.port, 8787); assert.equal(fileConfig.oauth.ownerToken, "persisted-owner-token-long-enough"); assert.equal(fileConfig.publicBaseUrl, "https://devspace.example.com"); +assert.equal(fileConfig.localAgents, true); assert.deepEqual(fileConfig.allowedHosts, [ "localhost", "127.0.0.1", diff --git a/src/config.ts b/src/config.ts index d065b17..2fb7ec9 100644 --- a/src/config.ts +++ b/src/config.ts @@ -3,7 +3,7 @@ import { join, resolve } from "node:path"; import { expandHomePath } from "./roots.js"; import type { LoggingConfig, LogFormat, LogLevel } from "./logger.js"; import type { OAuthConfig } from "./oauth-provider.js"; -import { loadDevspaceFiles } from "./user-config.js"; +import { devspaceSkillsDir, loadDevspaceFiles } from "./user-config.js"; export type ToolNamingMode = "legacy" | "short"; export type ToolMode = "minimal" | "full" | "codex"; @@ -25,6 +25,8 @@ export interface ServerConfig { worktreeRoot: string; skillsEnabled: boolean; skillPaths: string[]; + devspaceSkillsDir: string; + localAgents: boolean; agentDir: string; logging: LoggingConfig; } @@ -235,6 +237,11 @@ export function loadConfig(env: NodeJS.ProcessEnv = process.env): ServerConfig { worktreeRoot: resolve(expandHomePath(env.DEVSPACE_WORKTREE_ROOT ?? files.config.worktreeRoot ?? defaultWorktreeRoot())), skillsEnabled: env.DEVSPACE_SKILLS === undefined ? true : parseBoolean(env.DEVSPACE_SKILLS), skillPaths: parsePathList(env.DEVSPACE_SKILL_PATHS), + devspaceSkillsDir: devspaceSkillsDir(env), + localAgents: + env.DEVSPACE_LOCAL_AGENTS === undefined + ? files.config.localAgents === true + : parseBoolean(env.DEVSPACE_LOCAL_AGENTS), agentDir: resolve(expandHomePath(env.DEVSPACE_AGENT_DIR ?? files.config.agentDir ?? defaultAgentDir())), logging: parseLoggingConfig(env), }; diff --git a/src/skills.test.ts b/src/skills.test.ts index 16a49c8..af396bb 100644 --- a/src/skills.test.ts +++ b/src/skills.test.ts @@ -20,6 +20,7 @@ try { const projectRoot = join(root, "project"); const agentDir = join(root, "agent"); const explicitSkills = join(root, "explicit-skills"); + const devspaceSkills = join(root, ".devspace", "skills"); const globalAgentsSkills = join(root, ".agents", "skills"); const projectAgentsSkills = join(projectRoot, ".agents", "skills"); const globalClaudeSkills = join(root, ".claude", "skills"); @@ -30,8 +31,11 @@ try { await mkdir(join(projectClaudeSkills, "claude-project-skill"), { recursive: true }); await mkdir(join(projectRoot, ".pi", "skills", "project-skill"), { recursive: true }); await mkdir(join(agentDir, "skills", "global-skill"), { recursive: true }); + await mkdir(join(agentDir, "skills", "local-agent-delegation"), { recursive: true }); await mkdir(join(explicitSkills, "duplicate"), { recursive: true }); await mkdir(join(explicitSkills, "disabled"), { recursive: true }); + await mkdir(join(explicitSkills, "local-agent-delegation"), { recursive: true }); + await mkdir(join(devspaceSkills, "devspace-local-skill"), { recursive: true }); await writeFile( join(globalAgentsSkills, "agent-global-skill", "SKILL.md"), @@ -88,6 +92,17 @@ try { "# Project Skill", ].join("\n"), ); + await writeFile( + join(devspaceSkills, "devspace-local-skill", "SKILL.md"), + [ + "---", + "name: devspace-local-skill", + "description: DevSpace local skill description.", + "---", + "", + "# DevSpace Local Skill", + ].join("\n"), + ); await writeFile( join(agentDir, "skills", "global-skill", "SKILL.md"), [ @@ -110,6 +125,28 @@ try { "# Duplicate Skill", ].join("\n"), ); + await writeFile( + join(agentDir, "skills", "local-agent-delegation", "SKILL.md"), + [ + "---", + "name: local-agent-delegation", + "description: Hidden local agent skill winner.", + "---", + "", + "# Local Agent Delegation", + ].join("\n"), + ); + await writeFile( + join(explicitSkills, "local-agent-delegation", "SKILL.md"), + [ + "---", + "name: local-agent-delegation", + "description: Hidden local agent skill loser.", + "---", + "", + "# Local Agent Delegation Duplicate", + ].join("\n"), + ); await writeFile( join(explicitSkills, "disabled", "SKILL.md"), [ @@ -146,9 +183,31 @@ try { assert.equal(loaded.skills.some((skill) => skill.name === "claude-global-skill"), true); assert.equal(loaded.skills.some((skill) => skill.name === "claude-project-skill"), true); assert.equal(loaded.skills.some((skill) => skill.name === "project-skill"), false); + assert.equal(loaded.skills.some((skill) => skill.name === "devspace-local-skill"), true); + assert.equal(loaded.skills.some((skill) => skill.name === "local-agent-delegation"), false); assert.equal(loaded.skills.filter((skill) => skill.name === "duplicate-skill").length, 1); assert.equal(loaded.skills.some((skill) => skill.name === "hidden-skill"), true); assert.equal(loaded.diagnostics.some((diagnostic) => diagnostic.type === "collision"), true); + assert.equal( + loaded.diagnostics.some( + (diagnostic) => diagnostic.collision?.name === "local-agent-delegation", + ), + false, + ); + + const experimentalConfig = loadConfig({ + DEVSPACE_ALLOWED_ROOTS: projectRoot, + DEVSPACE_AGENT_DIR: agentDir, + DEVSPACE_LOCAL_AGENTS: "1", + DEVSPACE_OAUTH_OWNER_TOKEN: "test-owner-token-that-is-long-enough", + PORT: "1", + }); + assert.equal( + loadWorkspaceSkills(experimentalConfig, projectRoot).skills.some( + (skill) => skill.name === "local-agent-delegation", + ), + true, + ); const duplicateConfig = loadConfig({ DEVSPACE_ALLOWED_ROOTS: projectRoot, diff --git a/src/skills.ts b/src/skills.ts index e3da62f..c71b7ef 100644 --- a/src/skills.ts +++ b/src/skills.ts @@ -1,6 +1,7 @@ import { existsSync } from "node:fs"; import { homedir } from "node:os"; import { join, resolve, sep } from "node:path"; +import { fileURLToPath } from "node:url"; import { loadSkills, type Skill, @@ -20,12 +21,31 @@ export interface SkillReadResolution { isSkillFile: boolean; } +const LOCAL_AGENT_DELEGATION_NAME = "local-agent-delegation"; +const LOCAL_AGENT_DELEGATION_SKILL = join(LOCAL_AGENT_DELEGATION_NAME, "SKILL.md"); + +function bundledSkillsDir(): string { + return fileURLToPath(new URL("../skills", import.meta.url)); +} + +function hasLocalAgentDelegationSkill(skillDir: string): boolean { + return existsSync(join(skillDir, LOCAL_AGENT_DELEGATION_SKILL)); +} + export function effectiveSkillPaths(config: ServerConfig, cwd: string): string[] { - const defaultPaths = [ + const bundledSkills = bundledSkillsDir(); + const defaultPathCandidates = [ join(homedir(), ".agents", "skills"), resolve(cwd, ".agents", "skills"), + config.devspaceSkillsDir, join(config.agentDir, "skills"), - ].filter((path) => existsSync(path)); + config.localAgents && !hasLocalAgentDelegationSkill(config.devspaceSkillsDir) + ? bundledSkills + : undefined, + ]; + const defaultPaths = defaultPathCandidates.filter( + (path): path is string => path !== undefined && existsSync(path), + ); const seen = new Set(); return [...defaultPaths, ...config.skillPaths] @@ -44,12 +64,22 @@ function resolveSkillPath(path: string, cwd: string): string { export function loadWorkspaceSkills(config: ServerConfig, cwd: string): LoadedSkills { if (!config.skillsEnabled) return { skills: [], diagnostics: [] }; - return loadSkills({ + const result = loadSkills({ cwd, agentDir: config.agentDir, skillPaths: effectiveSkillPaths(config, cwd), includeDefaults: false, }); + + if (config.localAgents) return result; + + return { + skills: result.skills.filter((skill) => skill.name !== LOCAL_AGENT_DELEGATION_NAME), + diagnostics: result.diagnostics.filter((diagnostic) => { + const collision = diagnostic.collision; + return !(collision?.resourceType === "skill" && collision.name === LOCAL_AGENT_DELEGATION_NAME); + }), + }; } export function resolveSkillReadPath( diff --git a/src/user-config.ts b/src/user-config.ts index 0b79c51..b8c9a0b 100644 --- a/src/user-config.ts +++ b/src/user-config.ts @@ -6,7 +6,7 @@ import { writeFileSync, } from "node:fs"; import { homedir } from "node:os"; -import { join, resolve } from "node:path"; +import { dirname, join, resolve } from "node:path"; import { expandHomePath } from "./roots.js"; export interface DevspaceUserConfig { @@ -18,6 +18,7 @@ export interface DevspaceUserConfig { stateDir?: string; worktreeRoot?: string; agentDir?: string; + localAgents?: boolean; } export interface DevspaceAuthConfig { @@ -46,6 +47,10 @@ export function devspaceAuthPath(env: NodeJS.ProcessEnv = process.env): string { return join(devspaceConfigDir(env), "auth.json"); } +export function devspaceSkillsDir(env: NodeJS.ProcessEnv = process.env): string { + return join(devspaceConfigDir(env), "skills"); +} + export function loadDevspaceFiles(env: NodeJS.ProcessEnv = process.env): DevspaceFiles { const dir = devspaceConfigDir(env); const configPath = join(dir, "config.json"); @@ -88,6 +93,24 @@ export function generateOwnerToken(): string { return randomBytes(32).toString("base64url"); } +export function ensureDevspaceDefaultSkills(env: NodeJS.ProcessEnv = process.env): string[] { + const targetPath = join(devspaceSkillsDir(env), "local-agent-delegation", "SKILL.md"); + if (existsSync(targetPath)) return []; + + const sourcePath = new URL("../skills/local-agent-delegation/SKILL.md", import.meta.url); + mkdirSync(dirname(targetPath), { recursive: true }); + writeFileSync(targetPath, readFileSync(sourcePath, "utf8"), { mode: 0o644 }); + return [targetPath]; +} + +export function resolveLocalAgentsFlag( + config: Pick, + env: NodeJS.ProcessEnv = process.env, +): boolean | undefined { + if (env.DEVSPACE_LOCAL_AGENTS === undefined) return config.localAgents; + return ["1", "true", "yes", "on"].includes(env.DEVSPACE_LOCAL_AGENTS.toLowerCase()); +} + function readJsonFile(filePath: string): T { try { return JSON.parse(readFileSync(filePath, "utf8")) as T;