feat: add tiny local tool-use profile
TerminallyLazy committed
Jun 24, 2026 at 03:59 UTC
15aa7d71f090a43dfc697bba4375a7a7a2e45b9c
20 files changed
+354
-12
agents/AGENTS.md
+1
@@ -41,3 +41,4 @@ Direct child DOX files:
41
| [developer/AGENTS.md](developer/AGENTS.md) | Software development specialist profile. |
42
| [hacker/AGENTS.md](hacker/AGENTS.md) | Cyber security and penetration testing specialist profile. |
43
| [researcher/AGENTS.md](researcher/AGENTS.md) | Research, data analysis, and reporting specialist profile. |
44
+| [tiny-local/AGENTS.md](tiny-local/AGENTS.md) | Small/local model profile with an action-first communication prompt. |
agents/tiny-local/AGENTS.md
new
+36
@@ -0,0 +1,36 @@
1
+# Tiny Local Agent Profile DOX
2
+
3
+## Purpose
4
+
5
+- Own the bundled Tiny Local profile for small/local chat models.
6
+- Keep local-model behavior prompt-only and isolated from core framework execution.
7
+
8
+## Ownership
9
+
10
+- `agent.yaml` owns profile metadata for discovery and profile switching.
11
+- `prompts/agent.system.main.communication.md` owns the local-model communication contract.
12
+- `prompts/agent.system.main.solving.md` owns the local-model problem-solving contract and suppresses inherited visible reasoning requirements.
13
+- `prompts/agent.system.tools.md` owns the Tiny Local tools wrapper and final output-shape reminder after tool listing.
14
+- `prompts/agent.system.tool.*.md` files own Tiny Local-specific tool examples that avoid inherited reasoning fields and repeated writes.
15
+
16
+## Local Contracts
17
+
18
+- Preserve the normal Agent Zero tool-call shape: `tool_name` plus `tool_args`.
19
+- Do not add parser repair, duplicate suppression, model transport, memory, or text-editor runtime behavior here.
20
+- Keep prompt text short enough for small local models to follow.
21
+- Treat continuation requests such as `proceed` or `continue` as commands to execute the next unfinished step, not as prompts for another status response.
22
+- Do not include user-specific provider names, API keys, local paths, or secrets.
23
+
24
+## Work Guidance
25
+
26
+- Prefer prompt wording changes over new files when tightening this profile, except when replacing inherited tool examples for local-model compliance.
27
+- Keep this profile suitable for Ollama, LM Studio, Qwen, and comparable local models.
28
+
29
+## Verification
30
+
31
+- Render the `tiny-local` system prompt after communication prompt changes.
32
+- Run `pytest tests/test_default_prompt_budget.py` for prompt and profile regressions.
33
+
34
+## Child DOX Index
35
+
36
+No child DOX files.
agents/tiny-local/agent.yaml
new
+3
@@ -0,0 +1,3 @@
1
+title: Tiny Local
2
+description: Action-first profile for small local models that need a minimal tool-call contract.
3
+context: Use this agent when running small local chat models through Ollama, LM Studio, or similar providers and the model tends to explain actions instead of calling tools.
agents/tiny-local/prompts/agent.system.main.communication.md
new
+29
@@ -0,0 +1,29 @@
1
+## Communication
2
+
3
+You are Agent Zero. Act on the user's behalf.
4
+
5
+When the user asks you to do something, do it directly. Do not explain how the user could do it themselves.
6
+
7
+Your visible assistant message must be exactly one valid JSON object.
8
+
9
+Use exactly these top-level fields: `"tool_name"` and `"tool_args"`.
10
+
11
+Do not include markdown fences, prose before the JSON, prose after the JSON, hidden reasoning, analysis, thoughts, or headlines.
12
+
13
+Choose a tool from the tools listed in this system prompt. Do not invent tool names, action names, or generic names such as `read`, `write`, `terminal`, or `multi`.
14
+
15
+For a final user-facing answer, use the `response` tool.
16
+
17
+Use `response` only when the work is complete, blocked, or the user is only acknowledging completed work.
18
+
19
+If the user says "proceed", "continue", "go ahead", "do it", "excellent proceed", or similar after you named a next step or there is unfinished work, do not answer with a promise or status update. Call the next appropriate tool.
20
+
21
+Final-answer shape:
22
+
23
+`{"tool_name":"response","tool_args":{"text":"Answer briefly."}}`
24
+
25
+For work that requires a command, file action, browser action, or any other available tool, call the appropriate tool immediately. Do not explain what command the user could run manually.
26
+
27
+If the framework warns that your prior message was malformed, repeated, or reasoning-only, output a corrected JSON tool request immediately without explaining the warning.
28
+
29
+{{ include "agent.system.main.communication_additions.md" }}
agents/tiny-local/prompts/agent.system.main.solving.md
new
+17
@@ -0,0 +1,17 @@
1
+## Problem Solving
2
+
3
+Act directly and keep hidden reasoning out of the visible JSON.
4
+
5
+For simple questions, answer with the `response` tool.
6
+
7
+Continuation words such as "proceed", "continue", "go ahead", "do it", and "excellent proceed" mean execute the next unfinished step. Do not respond by saying you will begin, continue, start, proceed, or investigate. Use a real tool call unless the task is already complete or blocked.
8
+
9
+For tasks that need shell commands, files, browser actions, or other capabilities:
10
+- choose the appropriate listed tool immediately
11
+- keep one tool call per turn unless the `parallel` tool is listed and truly useful
12
+- inspect outputs before deciding the next tool call
13
+- never claim success from timeout output or a still-running command
14
+- after a successful tool result, do not repeat the same exact tool call
15
+- when finished, use the `response` tool with a brief result
16
+
17
+Do not include `thoughts`, `headline`, analysis, plans, or prose outside the JSON object.
agents/tiny-local/prompts/agent.system.tool.code_exe.md
new
+24
@@ -0,0 +1,24 @@
1
+### code_execution_tool
2
+Run terminal, Python, or Node.js commands.
3
+
4
+Arguments in `tool_args`:
5
+- `runtime`: `terminal`, `python`, `nodejs`, or `output`
6
+- `code`: command or script code
7
+- `session`: terminal session id; default `0`
8
+- `reset`: kill a session before running; `true` or `false`
9
+
10
+Rules:
11
+- Put the command or script in `code`.
12
+- Use `runtime=output` to poll running work.
13
+- Use `input` for interactive terminal prompts.
14
+- If a session is stuck, call this tool again with the same `session` and `reset=true`.
15
+- Do not claim success from timeout output or a still-running command.
16
+- When counting files, prefer `find` over `ls` so hidden files and type filters are handled.
17
+
18
+Examples:
19
+
20
+`{"tool_name":"code_execution_tool","tool_args":{"runtime":"terminal","session":0,"reset":false,"code":"ls -1 /tmp | wc -l"}}`
21
+
22
+`{"tool_name":"code_execution_tool","tool_args":{"runtime":"python","session":0,"reset":false,"code":"import os\nprint(os.getcwd())"}}`
23
+
24
+`{"tool_name":"code_execution_tool","tool_args":{"runtime":"output","session":0}}`
agents/tiny-local/prompts/agent.system.tool.response.md
new
+13
@@ -0,0 +1,13 @@
1
+### response
2
+Final answer to the user.
3
+
4
+Use this tool only when the task is done, blocked, or no tool is needed.
5
+
6
+Do not use this tool for "proceed", "continue", "go ahead", or similar continuation requests when there is an unfinished next step. Call a real tool instead.
7
+
8
+Arguments in `tool_args`:
9
+- `text`: concise final answer text
10
+
11
+Example:
12
+
13
+`{"tool_name":"response","tool_args":{"text":"There are 24 files in /tmp."}}`
agents/tiny-local/prompts/agent.system.tool.text_editor.md
new
+26
@@ -0,0 +1,26 @@
1
+### text_editor
2
+Read, write, or patch Markdown and plain text files.
3
+
4
+Actions in `tool_args.action`:
5
+- `read`: read a file
6
+- `write`: create or overwrite a file
7
+- `patch`: edit an existing file
8
+
9
+Common arguments:
10
+- `path`: absolute file path
11
+- `content`: full file content for `write`
12
+- `open_in_canvas`: set `true` when the user explicitly asks to open a Markdown file in the Canvas or Editor
13
+
14
+Rules:
15
+- Use this tool for `.md` and plain text files.
16
+- Use `write` to create a new Markdown file.
17
+- If the user asks to open the file in the Canvas or Editor, include `"open_in_canvas": true` in the same `write` or `patch` call.
18
+- After a successful write or patch result, do not repeat the same tool call. Use the `response` tool unless a different action is needed.
19
+
20
+Examples:
21
+
22
+`{"tool_name":"text_editor","tool_args":{"action":"write","path":"/a0/usr/workdir/TODO.md","content":"# TODO\n- [ ] First item\n","open_in_canvas":true}}`
23
+
24
+`{"tool_name":"text_editor","tool_args":{"action":"read","path":"/a0/usr/workdir/TODO.md"}}`
25
+
26
+`{"tool_name":"text_editor","tool_args":{"action":"patch","path":"/a0/usr/workdir/TODO.md","old_text":"- [ ] First item","new_text":"- [x] First item"}}`
agents/tiny-local/prompts/agent.system.tools.md
new
+17
@@ -0,0 +1,17 @@
1
+## Available Tools
2
+
3
+Use only the tools listed below. Match tool names exactly.
4
+
5
+Every tool request must be exactly one JSON object with only these top-level fields:
6
+- `tool_name`
7
+- `tool_args`
8
+
9
+Action names are not tool names. Do not invent top-level `multi`, `read`, `write`, `terminal`, or generic batch tools.
10
+
11
+{{tools}}
12
+
13
+## Tiny Local Output Rule
14
+
15
+Some inherited tool examples may show `thoughts` or `headline`. Ignore that shape for this profile.
16
+
17
+Do not include `thoughts`, `headline`, analysis, markdown fences, or prose outside the JSON object.
docs/guides/agent-profiles.md
+4
@@ -66,6 +66,10 @@ These controls are related, but they solve different problems.
66
| **Project** | Files, workspace, memories, instructions, secrets, and long-running context. |
67
| **Model Preset** | Which models are used for the chat. |
68
69
+For small local models that narrate instead of calling tools, use the bundled
70
+**Tiny Local** profile or the project-scoped Prompt Include recipe in
71
+[Local Model Tool Use](local-model-tool-use.md).
72
+
73
Example:
74
75
- use a **Project** for a client repository;
docs/guides/local-model-tool-use.md
new
+65
@@ -0,0 +1,65 @@
1
+# Local Model Tool Use
2
+
3
+Small local models can struggle with Agent Zero's full default communication shape. The safest first fix is prompt/profile/plugin-only: use a smaller behavior contract while leaving Agent Zero's core parser and execution code unchanged.
4
+
5
+Use this guide for Ollama, LM Studio, Qwen, and similar local chat models when the model explains commands instead of calling tools.
6
+
7
+## Use The Tiny Local Profile
8
+
9
+Choose the **Tiny Local** profile when starting or switching a chat that uses a small local model.
10
+
11
+The bundled profile lives at:
12
+
13
+```text
14
+agents/tiny-local/
15
+```
16
+
17
+Tiny Local keeps the normal Agent Zero tool-call shape, but removes visible reasoning fields from the communication prompt. It tells the model to emit one executable JSON object with `tool_name` and `tool_args`.
18
+
19
+## Use A Project Prompt Include
20
+
21
+If you want to keep your current profile, create a project-local file that matches the Prompt Include plugin pattern (`*.promptinclude.md`):
22
+
23
+```text
24
+local-model-tool-use.promptinclude.md
25
+```
26
+
27
+Put this content in that file:
28
+
29
+```markdown
30
+## Local model tool-use discipline
31
+
32
+You are Agent Zero. Act on the user's behalf.
33
+
34
+When the user asks you to do something, do it directly. Do not explain how the user could do it themselves.
35
+
36
+Your visible assistant message must be exactly one valid JSON object.
37
+
38
+Use exactly these top-level fields: `tool_name` and `tool_args`.
39
+
40
+Do not include markdown fences, prose before the JSON, prose after the JSON, hidden reasoning, analysis, thoughts, or headlines.
41
+
42
+Choose a tool from the tools listed in the system prompt. Do not invent tool names, action names, or generic names such as `read`, `write`, `terminal`, or `multi`.
43
+
44
+For a final user-facing answer, use the `response` tool:
45
+
46
+`{"tool_name":"response","tool_args":{"text":"Done."}}`
47
+
48
+Use `response` only when the work is complete, blocked, or no tool is needed. If the user says "proceed", "continue", "go ahead", or similar after the agent named a next step, call the next appropriate tool instead of replying with a promise or status update.
49
+
50
+For work that requires a command, file action, browser action, or any other available tool, call the appropriate tool immediately.
51
+
52
+If the framework warns that your prior message was malformed, repeated, or reasoning-only, output a corrected JSON tool request immediately without explaining the warning.
53
+```
54
+
55
+## Keep This Prompt-Only
56
+
57
+Do not change `agent.py` for this workflow.
58
+
59
+Do not change `helpers/extract_tools.py` for this workflow.
60
+
61
+Do not create parser repair code for this workflow.
62
+
63
+Do not add duplicate execution suppression, LiteLLM transport changes, memory runtime changes, or text-editor file operation changes for this workflow.
64
+
65
+If a specific local model still cannot follow the prompt/profile/plugin-only contract, capture the exact model, prompt, response, and tool warning before considering deeper framework changes.
plugins/_editor/AGENTS.md
+1
@@ -19,6 +19,7 @@
19
- Keep the floating Editor modal on the shared surface modal chrome so the header remains draggable while existing Focus mode continues to work.
20
- Keep Editor Open wired through the File Browser text picker so users can open one or more Markdown or plain text files with an obvious confirmation action.
21
- Keep Save As distinct from Rename: Save As writes the current editor text to a chosen `.md` or `.txt` path and retargets the active session without removing the original file.
22
+- Preserve source chat context ids when opening Markdown files from tool-result canvas handoffs.
23
24
## Work Guidance
25
plugins/_editor/extensions/webui/set_messages_after_loop/sync-text-editor-results.js
+17
-2
@@ -5,7 +5,7 @@ const SYNC_WINDOW_MS = 10 * 60 * 1000;
5
const syncedTextEditorResults = new Set();
6
7
export default async function syncTextEditorResultsIntoOpenEditor(context) {
8
- if (!context?.results?.length || context.historyEmpty) return;
8
+ if (!context?.results?.length) return;
9
10
for (const { args } of context.results) {
11
const payload = getTextEditorPayload(args);
@@ -14,6 +14,8 @@ export default async function syncTextEditorResultsIntoOpenEditor(context) {
14
15
const target = textEditorTarget(payload);
16
if (!target.path || target.extension !== "md") continue;
17
+ const explicitOpen = shouldOpenEditorUiFromResult(payload, target);
18
+ if (context.historyEmpty && !explicitOpen) continue;
19
20
const key = [
21
args?.id || "",
@@ -26,10 +28,12 @@ export default async function syncTextEditorResultsIntoOpenEditor(context) {
28
syncedTextEditorResults.add(key);
29
30
globalThis.setTimeout(() => {
29
- if (shouldOpenEditorUiFromResult(payload, target)) {
31
+ if (explicitOpen) {
32
void openSurface("editor", {
33
path: target.path || "",
34
file_id: target.file_id || "",
35
+ ctxid: target.ctxid || target.context_id || "",
36
+ context_id: target.context_id || target.ctxid || "",
37
refresh: true,
38
source: "tool-result-open",
39
});
@@ -60,6 +64,8 @@ function pickPayloadFields(args = {}) {
64
"action",
65
"extension",
66
"format",
67
+ "context_id",
68
+ "ctxid",
69
"last_modified",
70
"open_canvas",
71
"open_document",
@@ -97,9 +103,12 @@ function isExplicitEditorUiRequest(payload = {}) {
103
function textEditorTarget(payload = {}) {
104
const path = String(payload.path || "").trim();
105
const extension = documentExtension(payload, { path });
106
+ const contextId = contextIdFromPayload(payload);
107
return {
108
path,
109
file_id: "",
110
+ context_id: contextId,
111
+ ctxid: contextId,
112
extension,
113
format: extension,
114
version: payload.version || "",
@@ -139,6 +148,8 @@ async function syncOpenEditorSurface(document = {}) {
148
await editor.openSession?.({
149
path: document.path || "",
150
file_id: document.file_id || "",
151
+ ctxid: document.ctxid || document.context_id || "",
152
+ context_id: document.context_id || document.ctxid || "",
153
refresh: true,
154
source: "tool-result-sync",
155
});
@@ -175,6 +186,10 @@ function documentsMatch(entry = {}, document = {}) {
186
);
187
}
188
189
+function contextIdFromPayload(payload = {}) {
190
+ return String(payload.context_id || payload.ctxid || "").trim();
191
+}
192
+
193
function truthy(value) {
194
if (value === true) return true;
195
if (value === false || value == null) return false;
plugins/_editor/webui/editor-store.js
+7
-2
@@ -182,17 +182,19 @@ function taskLineIndexes(markdown = "") {
182
}
183
184
async function callEditor(action, payload = {}) {
185
+ const explicitContextId = String(payload.ctxid || payload.context_id || "").trim();
186
return await callJsonApi("/plugins/_editor/editor_session", {
187
action,
187
- ctxid: currentContextId(),
188
...payload,
189
+ ctxid: explicitContextId || currentContextId(),
190
});
191
}
192
193
async function requestEditor(eventType, payload = {}, timeoutMs = 5000) {
194
+ const explicitContextId = String(payload.ctxid || payload.context_id || "").trim();
195
const response = await editorSocket.request(eventType, {
194
- ctxid: currentContextId(),
196
...payload,
197
+ ctxid: explicitContextId || currentContextId(),
198
}, { timeoutMs });
199
const results = Array.isArray(response?.results) ? response.results : [];
200
const first = results.find((item) => item?.ok === true && isEditorSocketData(item?.data))
@@ -284,9 +286,12 @@ const model = {
286
await this.init();
287
await this.refresh();
288
if (payload?.path || payload?.file_id) {
289
+ const contextId = String(payload.ctxid || payload.context_id || "").trim();
290
await this.openSession({
291
path: payload.path || "",
292
file_id: payload.file_id || "",
293
+ ctxid: contextId,
294
+ context_id: contextId,
295
refresh: payload.refresh === true,
296
source: payload.source || "",
297
});
plugins/_text_editor/AGENTS.md
+1
@@ -16,6 +16,7 @@
16
- Preserve stale-read protection before patch operations.
17
- Validate patch structures before applying edits.
18
- Read back changed regions after writes or patches where the tool contract requires confirmation.
19
+- Include the active agent context id in write/patch result metadata when available so canvas consumers can open the changed file in the correct chat context.
20
21
## Work Guidance
22
plugins/_text_editor/tools/text_editor.py
+11
-6
@@ -152,7 +152,7 @@ class TextEditor(Tool):
152
return Response(
153
message=msg,
154
break_loop=False,
155
- additional=_result_additional("write", info, kwargs),
155
+ additional=_result_additional("write", info, kwargs, agent=self.agent),
156
)
157
158
# ------------------------------------------------------------------
@@ -256,7 +256,7 @@ class TextEditor(Tool):
256
return Response(
257
message=msg,
258
break_loop=False,
259
- additional=_result_additional("patch", post_info, options),
259
+ additional=_result_additional("patch", post_info, options, agent=self.agent),
260
)
261
262
async def _patch_replace(
@@ -315,7 +315,7 @@ class TextEditor(Tool):
315
return Response(
316
message=msg,
317
break_loop=False,
318
- additional=_result_additional("patch", post_info, options),
318
+ additional=_result_additional("patch", post_info, options, agent=self.agent),
319
)
320
321
async def _patch_context(
@@ -377,7 +377,7 @@ class TextEditor(Tool):
377
return Response(
378
message=msg,
379
break_loop=False,
380
- additional=_result_additional("patch", post_info, options),
380
+ additional=_result_additional("patch", post_info, options, agent=self.agent),
381
)
382
383
# ------------------------------------------------------------------
@@ -475,7 +475,7 @@ def _freshness_error_message(agent, info: FileInfo, code: str) -> str:
475
return agent.read_prompt(prompt, path=info["expanded"])
476
477
478
-def _result_additional(action: str, info: FileInfo, options: dict | None = None) -> dict:
478
+def _result_additional(action: str, info: FileInfo, options: dict | None = None, agent=None) -> dict:
479
path = str(info.get("expanded") or "")
480
extension = Path(path).suffix.lower().lstrip(".")
481
options = options or {}
@@ -484,7 +484,7 @@ def _result_additional(action: str, info: FileInfo, options: dict | None = None)
484
or options.get("open_canvas")
485
or options.get("open_document")
486
)
487
- return {
487
+ additional = {
488
"_tool_name": "text_editor",
489
"action": action,
490
"path": path,
@@ -492,6 +492,11 @@ def _result_additional(action: str, info: FileInfo, options: dict | None = None)
492
"extension": extension,
493
"open_in_canvas": open_in_canvas,
494
}
495
+ context_id = str(getattr(getattr(agent, "context", None), "id", "") or "").strip()
496
+ if context_id:
497
+ additional["context_id"] = context_id
498
+ additional["ctxid"] = context_id
499
+ return additional
500
501
502
def _truthy(value) -> bool:
tests/test_default_prompt_budget.py
+58
@@ -71,6 +71,64 @@ async def test_default_agent0_prompt_budget_and_guardrails():
71
assert "Computer Use enablement is scoped to the current CLI session" not in system_text
72
73
74
+@pytest.mark.asyncio
75
+async def test_tiny_local_profile_prompt_is_action_first_json_contract():
76
+ system_text = await _build_system_text("tiny-local")
77
+ communication_prompt = (
78
+ PROJECT_ROOT / "agents" / "tiny-local" / "prompts" / "agent.system.main.communication.md"
79
+ ).read_text(encoding="utf-8")
80
+ code_prompt = (
81
+ PROJECT_ROOT / "agents" / "tiny-local" / "prompts" / "agent.system.tool.code_exe.md"
82
+ ).read_text(encoding="utf-8")
83
+ response_prompt = (
84
+ PROJECT_ROOT / "agents" / "tiny-local" / "prompts" / "agent.system.tool.response.md"
85
+ ).read_text(encoding="utf-8")
86
+ text_editor_prompt = (
87
+ PROJECT_ROOT / "agents" / "tiny-local" / "prompts" / "agent.system.tool.text_editor.md"
88
+ ).read_text(encoding="utf-8")
89
+ solving_prompt = (
90
+ PROJECT_ROOT / "agents" / "tiny-local" / "prompts" / "agent.system.main.solving.md"
91
+ ).read_text(encoding="utf-8")
92
+
93
+ assert "You are Agent Zero. Act on the user's behalf." in system_text
94
+ assert "Your visible assistant message must be exactly one valid JSON object." in system_text
95
+ assert 'Use exactly these top-level fields: `"tool_name"` and `"tool_args"`.' in system_text
96
+ assert 'For a final user-facing answer, use the `response` tool.' in system_text
97
+ assert "Use `response` only when the work is complete, blocked, or the user is only acknowledging completed work." in system_text
98
+ assert "If the user says \"proceed\", \"continue\", \"go ahead\", \"do it\", \"excellent proceed\"" in system_text
99
+ assert "Do not explain what command the user could run manually." in system_text
100
+ assert "output a corrected JSON tool request immediately" in system_text
101
+ assert "## Tiny Local Output Rule" in system_text
102
+ assert "~~~json" not in communication_prompt
103
+ assert "~~~json" not in code_prompt
104
+ assert "~~~json" not in response_prompt
105
+ assert "~~~json" not in text_editor_prompt
106
+ assert "No JSON in markdown fences" not in communication_prompt
107
+ assert "thoughts: array thoughts before execution" not in communication_prompt
108
+ assert "headline: short headline summary" not in communication_prompt
109
+ assert "explain each step in thoughts" not in solving_prompt
110
+ assert "Continuation words" in solving_prompt
111
+ assert "Do not respond by saying you will begin, continue, start, proceed, or investigate." in solving_prompt
112
+ assert "Do not use this tool for \"proceed\", \"continue\", \"go ahead\"" in response_prompt
113
+ assert "do not repeat the same exact tool call" in solving_prompt
114
+ assert '"open_in_canvas":true' in text_editor_prompt
115
+ assert "do not repeat the same tool call" in text_editor_prompt
116
+ assert '"headline"' not in code_prompt
117
+ assert '"headline"' not in response_prompt
118
+ assert '"headline"' not in text_editor_prompt
119
+
120
+
121
+def test_tiny_local_profile_is_discoverable():
122
+ from helpers import subagents
123
+
124
+ profiles = {
125
+ str(item.get("key") or ""): str(item.get("label") or "")
126
+ for item in subagents.get_all_agents_list()
127
+ }
128
+
129
+ assert profiles["tiny-local"] == "Tiny Local"
130
+
131
+
132
def test_removed_small_profile_and_prompt_text_generic():
133
removed_profile = "a0" + "_" + "small"
134
tests/test_office_canvas_setup.py
+4
@@ -401,8 +401,12 @@ def test_office_artifacts_only_open_desktop_from_explicit_document_ui_requests()
401
assert "syncTextEditorResultsIntoOpenEditor" in editor_sync
402
assert 'toolName(payload) !== "text_editor"' in editor_sync
403
assert 'return ["write", "patch"].includes(action);' in editor_sync
404
+ assert "if (!context?.results?.length) return;" in editor_sync
405
+ assert "if (context.historyEmpty && !explicitOpen) continue;" in editor_sync
406
assert "syncOpenEditorSurface" in editor_sync
407
assert "isEditorSurfaceOpen" in editor_sync
408
+ assert "context_id" in editor_sync
409
+ assert "ctxid" in editor_sync
410
assert "void syncOpenDocumentSurfaces(target);" in auto_open
411
assert "void syncOpenDocumentSurfaces({ path, file_id: fileId });" not in auto_open
412
assert "editorStore" not in auto_open
tests/test_text_editor_context_patch.py
+5
-2
@@ -379,8 +379,9 @@ class _FakeTool:
379
380
381
class _FakeAgent:
382
- def __init__(self) -> None:
382
+ def __init__(self, context_id: str = "") -> None:
383
self.data = {}
384
+ self.context = types.SimpleNamespace(id=context_id) if context_id else None
385
386
def read_prompt(self, name: str, **kwargs) -> str:
387
if name.endswith("read_ok.md"):
@@ -556,7 +557,7 @@ def test_text_editor_write_result_carries_markdown_canvas_intent(
557
) -> None:
558
module, _calls = _load_text_editor_tool(monkeypatch)
559
target = tmp_path / "note.md"
559
- tool = module.TextEditor(_FakeAgent(), "text_editor", "write", {}, "", None)
560
+ tool = module.TextEditor(_FakeAgent("ctx-write-1"), "text_editor", "write", {}, "", None)
561
562
response = asyncio.run(
563
tool._write(
@@ -574,6 +575,8 @@ def test_text_editor_write_result_carries_markdown_canvas_intent(
575
"format": "md",
576
"extension": "md",
577
"open_in_canvas": True,
578
+ "context_id": "ctx-write-1",
579
+ "ctxid": "ctx-write-1",
580
}
581
582
tests/test_tool_action_contracts.py
+15
@@ -649,6 +649,21 @@ def test_tool_prompts_prevent_top_level_multi_tool():
649
assert 'Never use `tool_name: "multi"`' in browser_prompt
650
651
652
+def test_local_model_tool_use_guide_stays_prompt_profile_plugin_only():
653
+ guide = Path("docs/guides/local-model-tool-use.md").read_text(encoding="utf-8")
654
+
655
+ assert "Tiny Local" in guide
656
+ assert "agents/tiny-local/" in guide
657
+ assert "*.promptinclude.md" in guide
658
+ assert "Do not change `agent.py`" in guide
659
+ assert "Do not change `helpers/extract_tools.py`" in guide
660
+ assert "Use exactly these top-level fields: `tool_name` and `tool_args`." in guide
661
+ assert '{"tool_name":"response","tool_args":{"text":"Done."}}' in guide
662
+ assert "If the user says \"proceed\", \"continue\", \"go ahead\", or similar" in guide
663
+ assert "call the next appropriate tool instead of replying with a promise or status update" in guide
664
+ assert "Do not create parser repair code for this workflow." in guide
665
+
666
+
667
def _load_scheduler_tool(monkeypatch):
668
_install_tool_stub(monkeypatch)
669