Skip to content

Commit 5703668

Browse files
committed
feat(agents): Add guided custom Agent creation
- Add a built-in skill with mode and subagent templates, a grouped tool catalog, and prompt-writing guidance. - Validate agent definitions with a bundled Python script covering YAML structure, field values, scope, and built-in tool selection. - Watch user Agent files and refresh discovery on query, with scan fallback when watching is unavailable. - Serialize registry reloads and handle directory creation and replacement so mode changes appear when reopening the picker. - Add localized guidance for creating custom Agents through chat. - Cover templates, validation, file watching, and registry refresh with focused tests.
1 parent cc06a9a commit 5703668

19 files changed

Lines changed: 882 additions & 2 deletions

File tree

‎src/crates/assembly/core/AGENTS.md‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,13 @@ existing Agent tool test assembly):
245245
cargo test --locked -p openbitfun-core --no-default-features --features mcp-runtime,git --lib agentic::tools::product_runtime::
246246
```
247247

248+
User Agent directory watching and registry regressions (omit `file-watch` to
249+
exercise query-time discovery fallback):
250+
251+
```bash
252+
cargo test --locked -p openbitfun-core --no-default-features --features agent-runtime,git,file-watch --lib agentic::agents::registry::
253+
```
254+
248255
Skill discovery, installation provenance, and local/remote registry regressions:
249256

250257
```bash
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
---
2+
name: create-agent
3+
description: Create or update OpenBitFun custom modes and subagents. Turn the user's needs into a role prompt and a suitable tool selection, then save an agent definition. Use for reusable roles, not ordinary delegation to an existing subagent.
4+
---
5+
6+
# Create an OpenBitFun Agent
7+
8+
## Understand the role
9+
10+
Determine what work the agent should do, what it should deliver, and whether it may change files. Infer these from the request and ask only about missing choices that would materially change the role.
11+
12+
- Use a **mode** for a main conversation role the user selects directly. Modes are user-wide.
13+
- Use a **subagent** for a specialist that receives work from a parent agent. Choose project scope for project-specific roles and user scope for roles reused across projects.
14+
15+
## Choose tools and write the prompt
16+
17+
Read [the tool catalog](references/tool-catalog.md). Select the tools needed for the requested tasks and write their names into `tools`.
18+
19+
Adapt [the mode example](assets/mode.md) or [the subagent example](assets/subagent.md).
20+
21+
Write a description that makes the role easy to select. In the prompt, explain its responsibilities, useful domain-specific methods, and expected output. For a mode, describe how it collaborates with the user and incorporates feedback. For a subagent, define the delegated input, completion criteria, and what to return to the parent.
22+
23+
Avoid emoji in the prompt, including headings and examples, unless the user explicitly requests them.
24+
25+
Keep the prompt consistent with the tools. An implementation role needs write tools and `readonly: false`; a read-only investigator should not be instructed to edit files.
26+
27+
## Agent file format and save locations
28+
29+
Save one UTF-8 `<id>.md` file directly in the agents directory. Start with YAML frontmatter between `---` lines, followed by the role prompt. The bundled examples show the format.
30+
31+
### Save location
32+
33+
| Scope | Directory | Supported roles |
34+
| --- | --- | --- |
35+
| Project | `<workspace>/.openbitfun/agents/` | Subagent |
36+
| User on Windows | `%APPDATA%/openbitfun/agents/` | Mode or subagent |
37+
| User on macOS | `~/Library/Application Support/openbitfun/agents/` | Mode or subagent |
38+
| User on Linux | `$XDG_CONFIG_HOME/openbitfun/agents/`, or `~/.config/openbitfun/agents/` | Mode or subagent |
39+
40+
If `OPENBITFUN_USER_ROOT` is configured, use its `agents/` subdirectory for user roles. Use the environment where the role will be used. If the user has a custom data location, use that location. A mode cannot be installed at project scope.
41+
42+
### Fields
43+
44+
| Field | What to write |
45+
| --- | --- |
46+
| `schema_version` | `1`. |
47+
| `kind` | `mode` or `subagent`. Always specify it. |
48+
| `id` | A unique name starting with a letter and containing only letters, digits, `-`, or `_`. Prefer lowercase kebab-case and retain it when updating the role. Avoid IDs that differ only by letter case. |
49+
| `name` | A nonempty display name, in the user's language if appropriate. |
50+
| `description` | A short explanation of when to use this role. |
51+
| `tools` | A YAML list of exact names from [the tool catalog](references/tool-catalog.md). |
52+
| `readonly` | `true` for inspection-only work; `false` when the role needs to make changes. Modes default to `false`, subagents to `true`. |
53+
| `model` | Set only for a subagent when the user requests it. Use `primary`/`fast` for the configured primary/fast model, or `inherit` to use the parent session's model. For a specific model, call `ListModels` and use the returned `model_id`, not `model_name`. Omit for modes. |
54+
55+
Write the role prompt in the Markdown body, not in a YAML `prompt` field. Use individual tool names rather than tool-group labels. Skill selections and other application settings are not fields in this file.
56+
57+
### Tool defaults
58+
59+
Prefer an explicit tool list tailored to the role. If `tools` is omitted, these defaults apply:
60+
61+
- Mode: `Read`, `Glob`, `Grep`, `Write`, `Edit`, `Delete`, `ExecCommand`, `WriteStdin`, `ExecControl`, `Task`, `ListModels`, `Skill`, `WebSearch`, `WebFetch`, `get_goal`, `create_goal`, `update_goal`.
62+
- Subagent: `LS`, `Read`, `Glob`, `Grep`.
63+
64+
An explicit `tools: []` gives the role no tools. Setting `readonly: false` alone does not add write tools.
65+
66+
## Save and deliver
67+
68+
Choose a distinct ID and check for an existing file before writing. When updating a role, preserve its ID and unrelated settings. Save the Markdown file in the requested scope; if the user only asked for a proposal, provide the proposed definition.
69+
70+
For a remote environment, save to the intended machine or workspace. If that location cannot be accessed, deliver the file and explain where to place it.
71+
72+
Validate the saved file using the bundled script (paths below are relative to this skill directory):
73+
74+
```bash
75+
python scripts/validate_agent.py /path/to/agent.md --scope user
76+
```
77+
78+
Use `--scope project` for project subagents. The script requires PyYAML; if missing, install it with `python -m pip install PyYAML` in the Python environment used for validation. Fix reported errors and rerun until validation passes.
79+
80+
Check consistency with the requested role. Tell the user what changed, the affected file path, and the resulting capabilities. Mode additions, edits, and removals are reflected when reopening the mode picker; subagent changes are reflected on the next conversation turn. Report only activation or invocation checks actually performed.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
schema_version: 1
3+
kind: mode
4+
id: implementation-partner
5+
name: Implementation Partner
6+
description: Develop and maintain software changes through an ongoing conversation, from understanding the request to implementation and focused verification.
7+
---
8+
9+
Help the user turn software requirements into maintainable changes. Establish the intended behavior from the request and relevant code, and surface material ambiguity before making decisions that would change the scope.
10+
11+
Use the project's instructions and existing patterns to guide implementation. Explain meaningful tradeoffs, incorporate feedback, and keep changes focused on the requested outcome. When the user asks only for analysis or a proposal, deliver that analysis before editing.
12+
13+
Verify the behavior affected by the change with focused checks available in the environment. Report what changed, the evidence gathered, and any remaining uncertainty or work needed to use the result.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
schema_version: 1
3+
kind: subagent
4+
id: code-investigator
5+
name: Code Investigator
6+
description: Investigate a bounded code question and return relevant implementation paths, evidence, and unresolved questions to the parent agent.
7+
tools:
8+
- LS
9+
- Read
10+
- Glob
11+
- Grep
12+
readonly: true
13+
---
14+
15+
Investigate the code question delegated by the parent agent within its stated scope. Locate the relevant implementation, follow the important callers and data flow, and distinguish behavior established by source from assumptions that require runtime evidence.
16+
17+
Use read-only inspection. Do not edit files, run commands, or delegate additional work. If the task requires capabilities or context you do not have, return the specific gap and the next useful check to the parent.
18+
19+
Finish when you can answer the question with concrete file references or explain why the available evidence is insufficient. Return the answer, supporting paths and symbols, and any unresolved uncertainty. Keep the report focused on what the parent needs to continue.
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
# Tool catalog for choosing agent capabilities
2+
3+
Use these tables to select tools for the user's tasks, then put their exact names in the agent's `tools` list.
4+
5+
## Inspect and search
6+
7+
| Tool name | Purpose |
8+
| --- | --- |
9+
| `LS` | List files and directories. |
10+
| `Read` | Read file contents. |
11+
| `Glob` | Find files by path or filename patterns. |
12+
| `Grep` | Search workspace file contents using structured pattern matching. |
13+
14+
## File editing
15+
16+
| Tool name | Purpose |
17+
| --- | --- |
18+
| `Write` | Create or overwrite a file. |
19+
| `Edit` | Make targeted changes to existing file contents. |
20+
| `Delete` | Delete a file or directory. |
21+
22+
## Command execution
23+
24+
| Tool name | Purpose |
25+
| --- | --- |
26+
| `ExecCommand` | Run a shell command in the session's workspace environment. |
27+
| `WriteStdin` | Send input to or collect output from a running ExecCommand session. |
28+
| `ExecControl` | Interrupt or terminate a running ExecCommand session. |
29+
30+
## Agents and skills
31+
32+
| Tool name | Purpose |
33+
| --- | --- |
34+
| `Task` | Delegate work to a subagent task and collect its result. |
35+
| `ListModels` | List enabled OpenBitFun model configurations. |
36+
| `AgentWait` | Wait for selected background agent results. |
37+
| `Skill` | Discover and load reusable skills for specialized workflows. |
38+
39+
## Web access
40+
41+
| Tool name | Purpose |
42+
| --- | --- |
43+
| `WebSearch` | Search the web for current information and sources. |
44+
| `WebFetch` | Fetch a URL's content as raw text, Markdown, or JSON. |
45+
46+
## Goal management
47+
48+
| Tool name | Purpose |
49+
| --- | --- |
50+
| `create_goal` | Start a new active goal for the current session. |
51+
| `get_goal` | Read the current session's active goal. |
52+
| `update_goal` | Mark the current session's goal complete or blocked. |
53+
54+
## Image understanding
55+
56+
| Tool name | Purpose |
57+
| --- | --- |
58+
| `analyze_image` | Analyze an image file with the configured vision model. |
59+
| `view_image` | Attach an image file for the current model to inspect visually. |
60+
61+
## Interaction and canvas
62+
63+
| Tool name | Purpose |
64+
| --- | --- |
65+
| `AskUserQuestion` | Ask the user focused follow-up questions during execution. |
66+
| `GenerativeUI` | Render HTML or SVG visualizations and lightweight interactive widgets in chat. |
67+
| `CreateCanvas` | Create a Canvas artifact scoped to the session. |
68+
| `ReadCanvas` | Read Canvas metadata, diagnostics, and source. |
69+
| `PatchCanvas` | Apply targeted patches to an existing Canvas artifact. |
70+
| `UpdateCanvas` | Update an existing Canvas artifact. |
71+
72+
## Computer use and automation
73+
74+
| Tool name | Purpose |
75+
| --- | --- |
76+
| `ComputerUse` | Inspect the screen and control desktop input. |
77+
| `ControlHub` | Control built-in or external browsers and existing terminal sessions. |
78+
| `Playbook` | Retrieve predefined operation guides for common tasks. |
79+
80+
## OpenBitFun customization
81+
82+
| Tool name | Purpose |
83+
| --- | --- |
84+
| `OpenBitFunControl` | Discover and control OpenBitFun features and settings. |
85+
| `FrontendWorkbench` | Draft and hot-apply the packaged OpenBitFun frontend with rollback protection. |
86+
| `PublishAppearance` | Submit an Appearance package to the Skin market for review. |
87+
88+
## MiniApp development and publishing
89+
90+
| Tool name | Purpose |
91+
| --- | --- |
92+
| `InitMiniApp` | Create a new MiniApp skeleton in the Toolbox. |
93+
| `FinalizeMiniApp` | Finalize MiniApp file edits and refresh its open runtimes. |
94+
| `PublishMiniApp` | Submit an installed MiniApp to the market for review. |
95+
96+
## Other
97+
98+
| Tool name | Purpose |
99+
| --- | --- |
100+
| `TodoWrite` | Create and update the session's todo list. |
101+
| `Cron` | Manage scheduled jobs. |
102+
| `Worktree` | List worktrees and manage isolated sessions, branches, and worktree removal. |
103+
| `GetFileDiff` | Show a file's diff against its baseline snapshot or Git HEAD. |
104+
| `ReviewPlatform` | Inspect and operate on hosted pull requests or merge requests. |
105+
| `ListMCPResources` | List resources exposed by a connected MCP server. |
106+
| `ReadMCPResource` | Read a connected MCP server's resource by URI. |
107+
| `ListMCPPrompts` | List prompt templates exposed by a connected MCP server. |
108+
| `GetMCPPrompt` | Fetch and render a named prompt template from a connected MCP server. |
109+
| `SessionControl` | Create, list, rename, cancel, and delete persisted agent sessions. |
110+
| `SessionMessage` | Send a message to another agent session and receive its result asynchronously. |
111+
| `SessionHistory` | Export an agent session transcript and index for targeted history reads. |
112+
| `PortForward` | Forward a port from an SSH host to the user's machine. |
113+
| `PagePublish` | Upload, save a version of, and deploy an OpenBitFun Page. |
114+
| `PageDeploy` | Deploy a saved OpenBitFun Page version to production. |
115+
| `GetTime` | Return the current time, weekday, and Unix timestamp. |
116+
| `AgentSpawn` | Launch an agent to work independently in the background. |
117+
| `AgentSendInput` | Send an instruction to an existing agent. |
118+
| `AgentInterrupt` | Interrupt an agent's active background work. |
119+
| `AgentList` | List direct child agents and their status. |
120+
| `AgentDelete` | Permanently delete direct child agent subtrees. |
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import os
2+
import subprocess
3+
import sys
4+
import tempfile
5+
import unittest
6+
from pathlib import Path
7+
8+
from validate_agent import validate_agent
9+
10+
11+
ROOT = Path(__file__).resolve().parents[1]
12+
VALID = """---
13+
schema_version: 1
14+
kind: subagent
15+
id: investigator
16+
name: Investigator
17+
description: Investigate code
18+
tools: [Read, Grep]
19+
readonly: true
20+
---
21+
Inspect the requested code and report evidence.
22+
"""
23+
24+
25+
class AgentValidationTests(unittest.TestCase):
26+
def check_text(self, text, scope=None):
27+
with tempfile.TemporaryDirectory() as directory:
28+
path = Path(directory) / "agent.md"
29+
path.write_bytes(text.encode("utf-8"))
30+
before = path.read_bytes()
31+
errors = validate_agent(path, scope)
32+
self.assertEqual(path.read_bytes(), before)
33+
return errors
34+
35+
def test_bundled_templates(self):
36+
self.assertEqual(validate_agent(ROOT / "assets/mode.md", "user"), [])
37+
self.assertEqual(validate_agent(ROOT / "assets/subagent.md", "project"), [])
38+
39+
def test_optional_fields_and_line_endings(self):
40+
for change in [
41+
VALID.replace("tools: [Read, Grep]\n", ""),
42+
VALID.replace("[Read, Grep]", "[]"),
43+
VALID.replace("[Read, Grep]", "Read, Grep"),
44+
VALID.replace("readonly: true", "model: my-private-model-42"),
45+
VALID.replace("readonly: true", "model: inherit"),
46+
VALID.replace("\n", "\r\n"),
47+
]:
48+
with self.subTest(text=change):
49+
self.assertEqual(self.check_text(change), [])
50+
51+
def test_invalid_yaml_and_document_shape(self):
52+
for change in [
53+
VALID.replace("[Read, Grep]", "[Read"),
54+
VALID.replace("id: investigator", "id: investigator\nid: duplicate"),
55+
VALID.replace("readonly: true", "readonly: !!python/object:example {}"),
56+
"---\n[one, two]\n---\nPrompt", "---\n---\nPrompt",
57+
"\ufeff" + VALID, VALID.replace("---\n", "", 1),
58+
VALID.split("Inspect the requested")[0],
59+
]:
60+
with self.subTest(text=change):
61+
self.assertTrue(self.check_text(change))
62+
63+
def test_field_values(self):
64+
changes = [
65+
("schema_version: 1", "schema_version: true"),
66+
("schema_version: 1", "schema_version: 2"),
67+
("kind: subagent", "kind: other"),
68+
("id: investigator", "id: ../agent"),
69+
("name: Investigator", "name: null"),
70+
("description: Investigate code", 'description: ""'),
71+
("readonly: true", 'readonly: "true"'),
72+
("readonly: true", "readonly: yes"),
73+
("readonly: true", "model: 42"),
74+
("readonly: true", "model: null"),
75+
("readonly: true", "typo: true"),
76+
("[Read, Grep]", "[Read, UnknownTool]"),
77+
("[Read, Grep]", "[Read, mcp__example__tool]"),
78+
("[Read, Grep]", "[Read, 1]"),
79+
("[Read, Grep]", "null"),
80+
("[Read, Grep]", "[read]"),
81+
("readonly: true", "user_context_policy: [unknown]"),
82+
]
83+
for old, new in changes:
84+
with self.subTest(new=new):
85+
self.assertTrue(self.check_text(VALID.replace(old, new)))
86+
87+
def test_scope_and_review_kind(self):
88+
mode = VALID.replace("kind: subagent", "kind: mode")
89+
self.assertTrue(self.check_text(mode, "project"))
90+
self.assertEqual(self.check_text(mode, "user"), [])
91+
self.assertTrue(self.check_text(mode.replace("readonly: true", "review: true")))
92+
93+
def test_cli_exit_codes_and_missing_file(self):
94+
for name, expected in [("assets/mode.md", 0), ("missing.md", 1)]:
95+
result = subprocess.run(
96+
[sys.executable, str(ROOT / "scripts/validate_agent.py"), str(ROOT / name)],
97+
capture_output=True, text=True,
98+
creationflags=subprocess.CREATE_NO_WINDOW if os.name == "nt" else 0,
99+
)
100+
self.assertEqual(result.returncode, expected, result.stderr)
101+
self.assertNotIn("Traceback", result.stderr)
102+
103+
104+
if __name__ == "__main__":
105+
unittest.main()

0 commit comments

Comments
 (0)