Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
fc11ad4
Document agent runtime boundary design
hbanerjee74 Apr 30, 2026
97bd2f8
Add agent runtime boundary implementation plan
hbanerjee74 Apr 30, 2026
224df1a
Add sidecar runtime boundary primitives
hbanerjee74 Apr 30, 2026
bb30fd4
Send explicit runtime modes from Rust
hbanerjee74 Apr 30, 2026
9c2026b
Wrap Claude runtime boundary
hbanerjee74 Apr 30, 2026
00369f5
Document runtime boundary implementation
hbanerjee74 Apr 30, 2026
b9a70e6
Clean up Rust test warnings
hbanerjee74 Apr 30, 2026
4fed2c3
Make workflow runtime one-shot only
hbanerjee74 Apr 30, 2026
0c7e1e5
Allow null focus for unselected dimension scores
hbanerjee74 Apr 30, 2026
94e9837
Make frontend agent runtime APIs explicit
hbanerjee74 Apr 30, 2026
753fce2
Fix structured output failure handling
hbanerjee74 Apr 30, 2026
aa2e9a9
Use skill-builder identity for workflow runs
hbanerjee74 Apr 30, 2026
e630c03
Preserve workflow agent system prompt
hbanerjee74 Apr 30, 2026
82ca8dc
Parse one-shot JSON output at runtime boundary
hbanerjee74 Apr 30, 2026
9e96ebb
Show contradictory gate feedback in workflow
hbanerjee74 Apr 30, 2026
964e902
VU-1133: publish generated workflow skill output
hbanerjee74 May 1, 2026
fc947a8
VU-1133: clarify generated skill output directory
hbanerjee74 May 1, 2026
2c77024
VU-1133: remove version history selection controls
hbanerjee74 May 1, 2026
c578ce0
VU-1133: fix refine question follow-up UX
hbanerjee74 May 1, 2026
16386fd
VU-1133: commit refine edits during finalize
hbanerjee74 May 1, 2026
e2914e0
VU-1133: scope description optimization commits
hbanerjee74 May 1, 2026
dfe079a
chore(deps): bump @anthropic-ai/claude-agent-sdk in /app/sidecar
dependabot[bot] Apr 29, 2026
4e5ccaf
chore(deps): bump reqwest from 0.13.2 to 0.13.3 in /app/src-tauri
dependabot[bot] Apr 30, 2026
aed87da
chore(deps): bump uuid from 1.23.0 to 1.23.1 in /app/src-tauri
dependabot[bot] Apr 30, 2026
474a213
chore(deps): bump rand from 0.9.3 to 0.9.4 in /app/src-tauri
dependabot[bot] Apr 30, 2026
63daf2c
chore(deps): bump tauri from 2.10.2 to 2.11.0 in /app/src-tauri
dependabot[bot] Apr 30, 2026
16ad55a
chore(deps): bump tokio from 1.51.1 to 1.52.1 in /app/src-tauri
dependabot[bot] Apr 30, 2026
a4f9a98
VU-1133: fix description clippy warning
hbanerjee74 May 1, 2026
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
name: skill-builder
description: Executes one-shot workflow steps for building Claude Code skills and returns schema-conformant structured output.
model: sonnet
tools: Read, Write, Edit, Glob, Grep, Bash, Agent, Skill
---

# Skill Builder

You are an agent focused on building skills for use in Claude Code.

You run inside a desktop workflow that creates, refines, decides, and generates Claude Code skills. Treat the user's prompt as the authoritative workflow step instruction. Execute exactly that step and return the final payload required by the configured SDK `outputFormat`.

## Operating Rules

- Execute immediately. Do not greet the user, ask clarifying questions, or offer options.
- Use the workflow prompt to determine the current step, workspace paths, capability to invoke, and output shape.
- Keep the final answer as the step's JSON payload only. Do not wrap it in markdown or code fences.
- If you invoke another skill or agent that returns JSON, convert that returned payload into your own final response so the SDK can capture it as structured output.
- Do not use `AskUserQuestion`; workflow steps are one-shot runs.
- Do not read logs unless the workflow prompt explicitly asks for them.
- Do not create directories unless the workflow prompt explicitly asks for that.
- Use only the files named by the workflow step or by the invoked capability's instructions.

## Capability Routing

- Research steps may invoke `skill-content-researcher:research` with the `Skill` tool.
- Detailed research steps may invoke `skill-content-researcher:detailed-research` with the `Agent` tool when the workflow prompt requires detailed research behavior.
- Confirm decisions steps may invoke `skill-content-researcher:confirm-decisions` with the `Agent` tool when the workflow prompt requires decision confirmation behavior.
- Generate skill steps may invoke `skill-creator:generate-skill` with the `Skill` or `Agent` tool when the workflow prompt requires skill generation.

After any delegated capability completes, your own final response must be the required top-level JSON object for the workflow step.
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,10 @@
"description": "A scored dimension in the research plan.",
"properties": {
"focus": {
"type": "string"
"type": [
"string",
"null"
]
},
"name": {
"type": "string"
Expand All @@ -134,8 +137,7 @@
"required": [
"name",
"score",
"reason",
"focus"
"reason"
],
"type": "object"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,10 @@
"description": "A scored dimension in the research plan.",
"properties": {
"focus": {
"type": "string"
"type": [
"string",
"null"
]
},
"name": {
"type": "string"
Expand All @@ -126,8 +129,7 @@
"required": [
"name",
"score",
"reason",
"focus"
"reason"
],
"type": "object"
},
Expand Down
35 changes: 18 additions & 17 deletions agent-sources/plugins/skill-creator/agents/generate-skill.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ Instead, you:
1. gather the required local context and constraints for the skill to be created.
2. delegate the skill content creation work to `skill-creator:skill-creator` using the `Skill` tool
3. verify the delegated outputs against this prompt's requirements
4. apply finishing steps locally, including version bump, commit, tag, and final output formatting
4. apply finishing steps locally, including version metadata and final output formatting

You do NOT run evaluations or benchmarks — those are handled by a separate benchmark or description-optimization workflow.
You do NOT commit or tag the generated skill — the app commits the configured Skills Folder after materializing the step output.

</role>

Expand All @@ -35,6 +36,15 @@ You do NOT run evaluations or benchmarks — those are handled by a separate ben
- Derive `context_dir` as `workspace_dir/context`
- Derive `eval_dir` as `workspace_dir/evals` (`eval.json` **must** be created in this location)

## Directory Contract

The configured Skills Folder is authoritative for shipped skill files.

- Write `SKILL.md` only at `{skill_output_dir}/SKILL.md`.
- Write shipped references only under `{skill_output_dir}/references/`.
- Do not write shipped skill files under `{workspace_dir}` or `{workspace_dir}/skill`.
- Use `{workspace_dir}` only for workflow context, handoff files, logs, and eval artifacts such as `{eval_dir}/evals.json`.

</context>

---
Expand All @@ -57,7 +67,7 @@ Read `{context_dir}/decisions.json`. Parse the JSON.

If `metadata.contradictory_inputs == true` in `decisions.json`

- Write this stub to `SKILL.md` and return this JSON:
- Write this stub to `{skill_output_dir}/SKILL.md` and return this JSON:

```text
---
Expand Down Expand Up @@ -86,7 +96,7 @@ If `metadata.contradictory_inputs` is absent (the normal case), read `{context_d

If `metadata.scope_recommendation == true` in the parsed `clarifications.json`.

- Write this stub to `SKILL.md`
- Write this stub to `{skill_output_dir}/SKILL.md`

```text
---
Expand All @@ -107,7 +117,7 @@ The research planner determined the skill scope is too broad. See `clarification

### Malformed input

If any JSON file that is present is malformed, write this stub to `SKILL.md` and return this JSON:
If any JSON file that is present is malformed, write this stub to `{skill_output_dir}/SKILL.md` and return this JSON:

```text
---
Expand Down Expand Up @@ -192,8 +202,8 @@ After Phase 0-1 context gathering is complete, invoke the `skill-creator:skill-c

Delegate only the content-creation work:

- writing `SKILL.md`
- creating or updating referenced files
- writing `{skill_output_dir}/SKILL.md`
- creating or updating referenced files under `{skill_output_dir}/references/`
- writing `{eval_dir}/evals.json`
- incorporating decisions and clarifications into the generated skill content
- drafting the initial skill description from the `Write the SKILL.md` guidance in the `skill-creator` skill
Expand All @@ -220,18 +230,9 @@ Before committing:

If any check fails, fix the generated files before proceeding.

## Phase 4: Commit and tag

After all files are written, commit and tag the initial version:

```bash
cd "{skills_output_root}"
git -c user.email="agent@skillbuilder" -c user.name="Skill Builder" add "{skill_name}/"
git -c user.email="agent@skillbuilder" -c user.name="Skill Builder" commit -m "{skill_name}: {your commit_summary}"
git tag "{skill_name}/v1.0.0"
```
## Phase 4: Final response

If the commit reports "nothing to commit", skip tagging.
Do not run git commands. The app publishes and commits generated skill files in the configured Skills Folder after this agent returns structured output.

---

Expand Down
4 changes: 2 additions & 2 deletions agent-sources/plugins/skill-creator/agents/rewrite-skill.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,8 +215,8 @@ Perform a full preservation sweep to confirm no original domain knowledge was dr
After all file edits are complete, stage and commit:

```bash
git -c user.email="agent@skillbuilder" -c user.name="Skill Builder" add "{skill_name}/"
git -c user.email="agent@skillbuilder" -c user.name="Skill Builder" commit -m "{skill_name}: {your commit_summary}"
git -C "{skill_output_dir}/../.." -c user.email="agent@skillbuilder" -c user.name="Skill Builder" add "{skill_output_dir}/"
git -C "{skill_output_dir}/../.." -c user.email="agent@skillbuilder" -c user.name="Skill Builder" commit -m "{skill_name}: {your commit_summary}"
```

If the commit reports "nothing to commit", skip committing. Version tagging is handled automatically by the backend after the commit is detected.
Expand Down
10 changes: 10 additions & 0 deletions app/agent-tests/agent-structure.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const EXPECTED_AGENTS: string[] = [];

/** Plugin-hosted agents: agent name → plugin path relative to PLUGINS_DIR */
const PLUGIN_AGENTS: Record<string, string> = {
"skill-builder": "skill-content-researcher/agents/skill-builder.md",
"generate-skill": "skill-creator/agents/generate-skill.md",
"rewrite-skill": "skill-creator/agents/rewrite-skill.md",
"detailed-research": "skill-content-researcher/agents/detailed-research.md",
Expand Down Expand Up @@ -196,6 +197,15 @@ describe("Research scope guard contract prompts", () => {
// - materialize_answer_evaluation_output_value() → answer-evaluator path

describe("Agent output contracts (backend protocol alignment)", () => {
it("skill-builder is the one-shot workflow identity and returns delegated JSON as final output", () => {
const content = fs.readFileSync(resolveAgentPath("skill-builder"), "utf8");
expect(content).toMatch(/focused on building skills for use in Claude Code/i);
expect(content).toMatch(/workflow step instruction/i);
expect(content).toMatch(/return the final payload required by the configured SDK `outputFormat`/i);
expect(content).toMatch(/convert that returned payload into your own final response/i);
expect(content).toMatch(/Do not use `AskUserQuestion`/i);
});

it("confirm-decisions returns version/metadata/decisions shape", () => {
const content = fs.readFileSync(
resolveAgentPath("confirm-decisions"),
Expand Down
2 changes: 1 addition & 1 deletion app/e2e/evals/evals.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { waitForAppReady } from "../helpers/app-helpers.js";
async function getEvalRunAgentId(page: Page): Promise<string> {
const banner = page.getByTestId("evals-run-thinking");
await banner.waitFor({ timeout: 8_000 });
// data-agent-id is set after startAgent resolves — wait for it to be non-empty.
// data-agent-id is set after startOneShotAgent resolves — wait for it to be non-empty.
await expect(banner).not.toHaveAttribute("data-agent-id", "", { timeout: 5_000 });
const agentId = await banner.getAttribute("data-agent-id");
if (!agentId) throw new Error("Could not read agent ID from evals-run-thinking banner");
Expand Down
26 changes: 26 additions & 0 deletions app/sidecar/__tests__/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,32 @@ describe("parseSidecarConfig", () => {
expect(result.requiredPlugins).toEqual(["computer", "bash"]);
});

it("accepts explicit one-shot mode", () => {
const result = parseSidecarConfig({
prompt: "hello",
apiKey: "key",
workspaceRootDir: TEST_CWD,
workspaceSkillDir: TEST_CWD,
pluginSlug: "demo",
mode: "one-shot",
});

expect(result.mode).toBe("one-shot");
});

it("throws when mode is invalid", () => {
expect(() =>
parseSidecarConfig({
prompt: "hello",
apiKey: "key",
workspaceRootDir: TEST_CWD,
workspaceSkillDir: TEST_CWD,
pluginSlug: "demo",
mode: "interactive",
}),
).toThrow("mode must be one of");
});

it("accepts empty string workspace dirs (no strict validation)", () => {
// workspace dirs are type-checked but not value-validated — empty string passes.
// Callers are responsible for providing valid directories.
Expand Down
12 changes: 12 additions & 0 deletions app/sidecar/__tests__/mock-agent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,18 @@ describe("resolveStepTemplate", () => {
});

it("maps workflow step agents correctly", () => {
expect(resolveStepTemplate("skill-content-researcher:skill-builder")).toBe(
"step0-research",
);
expect(resolveStepTemplate("skill-content-researcher:skill-builder", { stepId: 1 })).toBe(
"step1-detailed-research",
);
expect(resolveStepTemplate("skill-content-researcher:skill-builder", { stepId: 2 })).toBe(
"step2-confirm-decisions",
);
expect(resolveStepTemplate("skill-content-researcher:skill-builder", { stepId: 3 })).toBe(
"step3-generate-skill",
);
expect(resolveStepTemplate("skill-content-researcher:detailed-research")).toBe(
"step1-detailed-research",
);
Expand Down
57 changes: 57 additions & 0 deletions app/sidecar/__tests__/persistent-mode.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,63 @@ describe("runPersistent", () => {
expect(di1.item.resultStatus).toBe("success");
});

it("rejects agent_request configs that explicitly ask for streaming mode", async () => {
const input = createInputStream([
JSON.stringify({
type: "agent_request",
request_id: "req_bad_mode",
config: {
mode: "streaming",
prompt: "test prompt",
apiKey: "sk-test",
workspaceRootDir: os.tmpdir(),
workspaceSkillDir: os.tmpdir(),
},
}),
JSON.stringify({ type: "shutdown" }),
]);
const exitFn = vi.fn();
const capture = captureStdout();

try {
await runPersistent(input, exitFn);
} finally {
capture.restore();
}

expect(capture.lines.join("\n")).toContain("agent_request requires one-shot mode");
expect(mockQuery).not.toHaveBeenCalled();
});

it("rejects stream_start configs that explicitly ask for one-shot mode", async () => {
const input = createInputStream([
JSON.stringify({
type: "stream_start",
request_id: "req_bad_stream_mode",
session_id: "sess_bad_mode",
config: {
mode: "one-shot",
prompt: "test prompt",
apiKey: "sk-test",
workspaceRootDir: os.tmpdir(),
workspaceSkillDir: os.tmpdir(),
},
}),
JSON.stringify({ type: "shutdown" }),
]);
const exitFn = vi.fn();
const capture = captureStdout();

try {
await runPersistent(input, exitFn);
} finally {
capture.restore();
}

expect(capture.lines.join("\n")).toContain("stream_start requires streaming mode");
expect(mockQuery).not.toHaveBeenCalled();
});

it("handles SDK errors per-request without crashing", async () => {
mockQuery.mockImplementation(() => {
throw new Error("SDK connection failed");
Expand Down
18 changes: 18 additions & 0 deletions app/sidecar/__tests__/run-agent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,24 @@ describe("runAgentRequest", () => {
);
});

it("rejects one-shot requests that include AskUserQuestion", async () => {
const messages: Record<string, unknown>[] = [];

await runAgentRequest(
baseConfig({ mode: "one-shot", allowedTools: ["Read", "AskUserQuestion"] }),
(msg) => messages.push(msg),
);

const runResult = findRunResult(messages);

expect(mockQuery).not.toHaveBeenCalled();
expect(runResult).toBeDefined();
expect((runResult!.event as Record<string, unknown>).status).toBe("error");
expect(JSON.stringify(runResult)).toContain(
"one-shot runtime requests cannot include user-question tools",
);
});

it("streams all messages to the onMessage callback", async () => {
// Use proper SDK message shapes that MessageProcessor can process
const sdkMessages = [
Expand Down
Loading
Loading