mirror of
https://github.com/agent0ai/agent-zero.git
synced 2026-07-09 17:08:29 +00:00
Add protocol prompt area before history
Introduce a new prompt "protocol" separate from extras: add LoopData.protocol_temporary and protocol_persistent, include protocol contents before message history during prompt construction, and clear temporary protocol data each turn. Add helper _build_context_message to render protocol/extras, update response input conversion to include protocol, and move project instructions & active/loaded skills injection into protocol. Update docs, prompts, plugin metadata, the SkillsTool message, and add tests to verify protocol placement and behavior.
This commit is contained in:
parent
bf2741990a
commit
afdc3aeb44
24 changed files with 236 additions and 53 deletions
|
|
@ -96,7 +96,7 @@ When running in Docker, Agent Zero uses two distinct Python runtimes to isolate
|
|||
```
|
||||
|
||||
Key Files:
|
||||
- agent.py: Defines AgentContext and the main Agent class.
|
||||
- agent.py: Defines AgentContext, LoopData virtual prompt areas (Protocol before history and Extras after history), and the main Agent class.
|
||||
- helpers/plugins.py: Plugin discovery and configuration logic.
|
||||
- webui/js/AlpineStore.js: Store factory for reactive frontend state.
|
||||
- helpers/api.py: Base class for all API endpoints.
|
||||
|
|
|
|||
68
agent.py
68
agent.py
|
|
@ -338,6 +338,8 @@ class LoopData:
|
|||
self.system = []
|
||||
self.user_message: history.Message | None = None
|
||||
self.history_output: list[history.OutputMessage] = []
|
||||
self.protocol_temporary: OrderedDict[str, history.MessageContent] = OrderedDict()
|
||||
self.protocol_persistent: OrderedDict[str, history.MessageContent] = OrderedDict()
|
||||
self.extras_temporary: OrderedDict[str, history.MessageContent] = OrderedDict()
|
||||
self.extras_persistent: OrderedDict[str, history.MessageContent] = OrderedDict()
|
||||
self.last_response = ""
|
||||
|
|
@ -580,24 +582,28 @@ class Agent:
|
|||
# concatenate system prompt
|
||||
system_text = "\n\n".join(loop_data.system)
|
||||
|
||||
# join extras
|
||||
extras = history.Message( # type: ignore[abstract]
|
||||
False,
|
||||
content=self.read_prompt(
|
||||
"agent.context.extras.md",
|
||||
extras=dirty_json.stringify(
|
||||
{**loop_data.extras_persistent, **loop_data.extras_temporary}
|
||||
),
|
||||
),
|
||||
).output()
|
||||
# join protocol and extras
|
||||
protocol = self._build_context_message(
|
||||
"agent.context.protocol.md",
|
||||
"protocol",
|
||||
{**loop_data.protocol_persistent, **loop_data.protocol_temporary},
|
||||
include_empty=False,
|
||||
)
|
||||
extras = self._build_context_message(
|
||||
"agent.context.extras.md",
|
||||
"extras",
|
||||
{**loop_data.extras_persistent, **loop_data.extras_temporary},
|
||||
include_empty=True,
|
||||
)
|
||||
loop_data.protocol_temporary.clear()
|
||||
loop_data.extras_temporary.clear()
|
||||
|
||||
# convert history + extras to LLM format
|
||||
# convert protocol + history + extras to LLM format
|
||||
history_langchain: list[BaseMessage] = history.output_langchain(
|
||||
loop_data.history_output + extras
|
||||
protocol + loop_data.history_output + extras
|
||||
)
|
||||
|
||||
# build full prompt from system prompt, message history and extrS
|
||||
# build full prompt from system prompt, protocol, message history and extras
|
||||
full_prompt: list[BaseMessage] = [
|
||||
SystemMessage(content=system_text),
|
||||
*history_langchain,
|
||||
|
|
@ -615,6 +621,24 @@ class Agent:
|
|||
|
||||
return full_prompt
|
||||
|
||||
def _build_context_message(
|
||||
self,
|
||||
prompt_file: str,
|
||||
variable_name: str,
|
||||
values: dict[str, history.MessageContent],
|
||||
include_empty: bool,
|
||||
) -> list[history.OutputMessage]:
|
||||
if not include_empty and not values:
|
||||
return []
|
||||
|
||||
return history.Message( # type: ignore[abstract]
|
||||
False,
|
||||
content=self.read_prompt(
|
||||
prompt_file,
|
||||
**{variable_name: dirty_json.stringify(values)},
|
||||
),
|
||||
).output()
|
||||
|
||||
@extension.extensible
|
||||
async def handle_exception(self, location: str, exception: Exception):
|
||||
if exception:
|
||||
|
|
@ -920,9 +944,9 @@ class Agent:
|
|||
model,
|
||||
history_counter,
|
||||
)
|
||||
call_data["responses_local_input_items"] = (
|
||||
self._responses_static_prefix_items(model, messages)
|
||||
+ self._responses_input_items_since(model, 0)
|
||||
call_data["responses_local_input_items"] = self._responses_prompt_input_items(
|
||||
model,
|
||||
messages,
|
||||
)
|
||||
|
||||
await extension.call_extensions_async(
|
||||
|
|
@ -1014,18 +1038,12 @@ class Agent:
|
|||
return ResponsesTransport.input_from_messages(converted)
|
||||
return []
|
||||
|
||||
def _responses_static_prefix_items(
|
||||
def _responses_prompt_input_items(
|
||||
self, model: Any, messages: list[BaseMessage]
|
||||
) -> list[dict[str, Any]]:
|
||||
prefix: list[BaseMessage] = []
|
||||
for message in messages:
|
||||
if isinstance(message, SystemMessage):
|
||||
prefix.append(message)
|
||||
continue
|
||||
break
|
||||
if not prefix or not hasattr(model, "_convert_messages"):
|
||||
if not hasattr(model, "_convert_messages"):
|
||||
return []
|
||||
converted = model._convert_messages(prefix)
|
||||
converted = model._convert_messages(messages)
|
||||
return ResponsesTransport.input_from_messages(converted)
|
||||
|
||||
def _remember_llm_result_state(
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ docs focus on practical setup, screenshots, and user workflows.
|
|||
- **[Desktop Guide](guides/desktop.md):** Use the built-in Linux desktop, GUI apps, and LibreOffice Writer/Calc/Impress Cowork.
|
||||
- **[A0 CLI Connector](guides/a0-cli-connector.md):** Terminal-first host connector for Agent Zero, with screenshots of the host picker, connected shell, command palette, and Browser modes.
|
||||
- **[Create a Small Plugin](guides/create-plugin.md):** Build and review a tiny Web UI plugin that adds an unread dot to the chat list.
|
||||
- **[Skills Guide](guides/skills.md):** Open the Skills selector, add active skills, and remove prompt extras you no longer need.
|
||||
- **[Skills Guide](guides/skills.md):** Open the Skills selector, add active skills, and remove prompt protocol entries you no longer need.
|
||||
- **[Agent Profiles](guides/agent-profiles.md):** Switch the current chat profile or create a new guided profile from the chat input.
|
||||
- **[Model Presets](guides/model-presets.md):** Create simple named shortcuts for model setups.
|
||||
- **[Memory Guide](guides/memory.md):** Search, edit, delete, and curate memories so useful context does not become stale noise.
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ You can always rename them later.
|
|||
| **Model Preset** | Which models power the chat. |
|
||||
| **Agent Profile** | The agent's role, tone, and prompt behavior. |
|
||||
| **Project** | Workspace, files, memory, secrets, and project instructions. |
|
||||
| **Skill** | A specific procedure added to prompt extras. |
|
||||
| **Skill** | A specific procedure added to prompt protocol. |
|
||||
|
||||
For example, you can use the same "Researcher" Agent Profile with a cheaper
|
||||
preset for simple questions and a stronger preset for difficult investigations.
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ Click a skill to add it. Active skills are shown at the top of the selector.
|
|||
To remove a skill, use the remove button in **Active skills** or uncheck it in
|
||||
the list.
|
||||
|
||||
Active skills are added to the **Extras** part of the system prompt. That means
|
||||
Active skills are added to the **Protocol** part of the prompt. That means
|
||||
Agent Zero sees them every turn while they are active.
|
||||
|
||||
> [!TIP]
|
||||
|
|
@ -56,7 +56,7 @@ usually easier for the agent to follow.
|
|||
|
||||
| Control | What it changes |
|
||||
| --- | --- |
|
||||
| **Skills** | Adds a specific procedure to the current prompt extras. |
|
||||
| **Skills** | Adds a specific procedure to the current prompt protocol. |
|
||||
| **Agent Profiles** | Changes the broader role and behavior of the chat. |
|
||||
| **Projects** | Adds workspace, files, memory, secrets, and project instructions. |
|
||||
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ Use the selector to add or remove active skills.
|
|||
|
||||

|
||||
|
||||
Active skills are added to the **Extras** part of the system prompt, so keep the
|
||||
Active skills are added to the **Protocol** part of the prompt, so keep the
|
||||
list short and intentional. See the [Skills guide](skills.md).
|
||||
|
||||
### Agent Profiles
|
||||
|
|
|
|||
|
|
@ -141,7 +141,7 @@ Explains how to search, edit, delete, export, and curate memories before stale c
|
|||
|
||||
### [Open A0 Skills Guide](guides/skills.md)
|
||||
|
||||
Shows the chat input **+** menu, the Skills selector, and how active skills are added to prompt extras.
|
||||
Shows the chat input **+** menu, the Skills selector, and how active skills are added to prompt protocol.
|
||||
|
||||
### [Open A0 Agent Profiles Guide](guides/agent-profiles.md)
|
||||
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ Direct child DOX files:
|
|||
| [hist_add_tool_result/AGENTS.md](hist_add_tool_result/AGENTS.md) | Tool-result history side effects. |
|
||||
| [job_loop/AGENTS.md](job_loop/AGENTS.md) | Periodic backend maintenance jobs. |
|
||||
| [message_loop_end/AGENTS.md](message_loop_end/AGENTS.md) | End-of-message-loop history and persistence behavior. |
|
||||
| [message_loop_prompts_after/AGENTS.md](message_loop_prompts_after/AGENTS.md) | Prompt extras appended after message-loop prompt construction. |
|
||||
| [message_loop_prompts_after/AGENTS.md](message_loop_prompts_after/AGENTS.md) | Prompt protocol and extras assembled around message-loop prompt construction. |
|
||||
| [message_loop_prompts_before/AGENTS.md](message_loop_prompts_before/AGENTS.md) | Pre-prompt-construction message-loop gates. |
|
||||
| [message_loop_start/AGENTS.md](message_loop_start/AGENTS.md) | Start-of-message-loop iteration state. |
|
||||
| [monologue_end/AGENTS.md](monologue_end/AGENTS.md) | End-of-monologue UI and cleanup behavior. |
|
||||
|
|
|
|||
|
|
@ -2,11 +2,12 @@
|
|||
|
||||
## Purpose
|
||||
|
||||
- Own prompt extras appended after primary message-loop prompt construction.
|
||||
- Own prompt protocol and extras appended around primary message-loop prompt construction.
|
||||
|
||||
## Ownership
|
||||
|
||||
- Ordered Python files own current datetime, skill recall/load context, agent info, parallel job status, and workdir extras injection.
|
||||
- Loaded and active skill instructions belong in prompt protocol, not prompt extras.
|
||||
|
||||
## Local Contracts
|
||||
|
||||
|
|
@ -16,11 +17,11 @@
|
|||
|
||||
## Work Guidance
|
||||
|
||||
- Coordinate prompt-extra changes with skill, workdir, and profile contracts.
|
||||
- Coordinate prompt protocol and prompt-extra changes with skill, workdir, and profile contracts.
|
||||
|
||||
## Verification
|
||||
|
||||
- Inspect rendered prompt extras or run prompt-construction tests after changes.
|
||||
- Inspect rendered prompt protocol/extras or run prompt-construction tests after changes.
|
||||
|
||||
## Child DOX Index
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@ class IncludeLoadedSkills(Extension):
|
|||
if not self.agent:
|
||||
return
|
||||
|
||||
extras = loop_data.extras_persistent
|
||||
protocol = loop_data.protocol_persistent
|
||||
protocol.pop("loaded_skills", None)
|
||||
|
||||
# Get loaded skills names
|
||||
skill_names = self.agent.data.get(DATA_NAME_LOADED_SKILLS)
|
||||
|
|
@ -31,8 +32,8 @@ class IncludeLoadedSkills(Extension):
|
|||
return
|
||||
|
||||
|
||||
# Inject into extras
|
||||
extras["loaded_skills"] = self.agent.read_prompt(
|
||||
# Inject into protocol
|
||||
protocol["loaded_skills"] = self.agent.read_prompt(
|
||||
"agent.system.skills.loaded.md",
|
||||
skills=content,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
## Ownership
|
||||
|
||||
- Ordered Python files own main, tools, MCP, secrets, skills, and project prompt sections.
|
||||
- Active project instruction bodies are moved into prompt protocol; the system prompt keeps project metadata and stable project rules.
|
||||
|
||||
## Local Contracts
|
||||
|
||||
|
|
|
|||
|
|
@ -15,17 +15,24 @@ class ProjectPrompt(Extension):
|
|||
):
|
||||
if not self.agent:
|
||||
return
|
||||
prompt = await build_prompt(self.agent)
|
||||
prompt = await build_prompt(self.agent, loop_data=loop_data)
|
||||
if prompt:
|
||||
system_prompt.append(prompt)
|
||||
|
||||
|
||||
@extensible
|
||||
async def build_prompt(agent: Agent) -> str:
|
||||
async def build_prompt(agent: Agent, loop_data: LoopData | None = None) -> str:
|
||||
result = agent.read_prompt("agent.system.projects.main.md")
|
||||
project_name = agent.context.get_data(projects.CONTEXT_DATA_KEY_PROJECT)
|
||||
if loop_data:
|
||||
loop_data.protocol_persistent.pop("project_instructions", None)
|
||||
if project_name:
|
||||
project_vars = projects.build_system_prompt_vars(project_name)
|
||||
if loop_data and project_vars.get("project_instructions"):
|
||||
loop_data.protocol_persistent["project_instructions"] = agent.read_prompt(
|
||||
"agent.protocol.projects.instructions.md",
|
||||
**project_vars,
|
||||
)
|
||||
result += "\n\n" + agent.read_prompt(
|
||||
"agent.system.projects.active.md", **project_vars
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
## Purpose
|
||||
|
||||
- Own active and hidden skill configuration injected into prompt extras on each turn.
|
||||
- Own active and hidden skill configuration injected into prompt protocol on each turn.
|
||||
|
||||
## Ownership
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ Skills is a built-in Agent Zero plugin that manages active skills across scope d
|
|||
|
||||
- pins default skills for the current plugin scope
|
||||
- hides noisy skills from the model-facing available catalog, skill search, and load access
|
||||
- injects the effective active skills into prompt extras on every turn
|
||||
- injects the effective active skills into prompt protocol on every turn
|
||||
- extends the same config screen with a current-chat mode so users can activate or hide skills live per conversation
|
||||
- supports global and project scoped configurations without agent-profile variants
|
||||
- links directly to the built-in Skills list
|
||||
|
|
@ -21,7 +21,7 @@ The shared active-skill state and prompt-resolution logic live in `helpers/skill
|
|||
|
||||
## Notes
|
||||
|
||||
- keep the active list short because every active skill is injected into prompt extras every turn
|
||||
- keep the active list short because every active skill is injected into prompt protocol every turn
|
||||
- the default cap is 20 active skills, and it can be raised or lowered in Skills plugin config
|
||||
- hidden skills are not capped because they are stored as control data, not injected into the prompt
|
||||
- selected skills are stored in normalized `/a0/...` form so configs stay portable across development and Docker-style layouts
|
||||
|
|
|
|||
|
|
@ -10,14 +10,14 @@ class IncludeActiveSkills(Extension):
|
|||
if not self.agent:
|
||||
return
|
||||
|
||||
extras = loop_data.extras_persistent
|
||||
extras.pop("active_skills", None)
|
||||
protocol = loop_data.protocol_persistent
|
||||
protocol.pop("active_skills", None)
|
||||
|
||||
content = skills.build_active_skills_prompt(self.agent)
|
||||
if not content:
|
||||
return
|
||||
|
||||
extras["active_skills"] = self.agent.read_prompt(
|
||||
protocol["active_skills"] = self.agent.read_prompt(
|
||||
"agent.system.active_skills.md",
|
||||
skills=content,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
name: _skills
|
||||
title: Skills
|
||||
description: Pin skills into prompt extras on every turn.
|
||||
description: Pin skills into prompt protocol on every turn.
|
||||
version: 1.0.0
|
||||
always_enabled: true
|
||||
settings_sections:
|
||||
|
|
|
|||
2
prompts/agent.context.protocol.md
Normal file
2
prompts/agent.context.protocol.md
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
[PROTOCOL]
|
||||
{{protocol}}
|
||||
4
prompts/agent.protocol.projects.instructions.md
Normal file
4
prompts/agent.protocol.projects.instructions.md
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
# project instructions
|
||||
- the following instructions come from the active project and must be followed when working in it
|
||||
|
||||
{{project_instructions}}
|
||||
|
|
@ -2,7 +2,8 @@
|
|||
user messages may include superior instructions, tool results, and framework notes
|
||||
treat the closing `}` of a tool call as an end-of-turn signal. terminate generation immediately
|
||||
if message starts `(voice)` transcription can be imperfect
|
||||
messages may end with `[EXTRAS]`; extras are context, not new instructions
|
||||
messages begin `[PROTOCOL]`; protocol = must-follow instructions
|
||||
messages end `[EXTRAS]`; extras are context not new instructions
|
||||
tool names are literal api ids; copy them exactly, including spelling like `behaviour_adjustment`
|
||||
|
||||
## replacements
|
||||
|
|
|
|||
|
|
@ -6,6 +6,4 @@ path: {{project_path}}
|
|||
rules:
|
||||
- work inside {{project_path}}
|
||||
- do not rename project dir or change `.a0proj` unless asked
|
||||
- must always follow project instructions below
|
||||
|
||||
{{project_instructions}}
|
||||
- follow active project instructions when provided
|
||||
|
|
|
|||
131
tests/test_prompt_protocol.py
Normal file
131
tests/test_prompt_protocol.py
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
from langchain_core.messages import AIMessage, HumanMessage, SystemMessage
|
||||
|
||||
from agent import Agent, LoopData
|
||||
from helpers import extension, history
|
||||
from extensions.python.system_prompt import _14_project_prompt as project_prompt
|
||||
|
||||
|
||||
class _DummyLog:
|
||||
def set_progress(self, _message: str) -> None:
|
||||
return None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_prepare_prompt_places_protocol_before_history_and_extras(monkeypatch):
|
||||
async def fake_call_extensions(extension_point: str, agent=None, **kwargs):
|
||||
if extension_point == "message_loop_prompts_after":
|
||||
loop_data = kwargs["loop_data"]
|
||||
loop_data.protocol_persistent["project_instructions"] = "Project rule."
|
||||
loop_data.extras_temporary["current_datetime"] = "Today."
|
||||
|
||||
monkeypatch.setattr(extension, "call_extensions_async", fake_call_extensions)
|
||||
monkeypatch.setattr(history.History, "_get_max_embeds", lambda self: 0)
|
||||
|
||||
agent = object.__new__(Agent)
|
||||
loop_data = LoopData()
|
||||
agent.loop_data = loop_data
|
||||
agent.context = SimpleNamespace(log=_DummyLog())
|
||||
agent.history = history.History(agent)
|
||||
agent.data = {}
|
||||
|
||||
agent.history.add_message(False, "User asks.")
|
||||
agent.history.add_message(True, "Assistant answers.")
|
||||
|
||||
async def get_system_prompt(_loop_data):
|
||||
return ["System root."]
|
||||
|
||||
def read_prompt(prompt_file: str, **kwargs) -> str:
|
||||
if prompt_file == "agent.context.protocol.md":
|
||||
return "[PROTOCOL]\n" + kwargs["protocol"]
|
||||
if prompt_file == "agent.context.extras.md":
|
||||
return "[EXTRAS]\n" + kwargs["extras"]
|
||||
raise AssertionError(f"Unexpected prompt file: {prompt_file}")
|
||||
|
||||
agent.get_system_prompt = get_system_prompt
|
||||
agent.read_prompt = read_prompt
|
||||
agent.set_data = lambda key, value: agent.data.__setitem__(key, value)
|
||||
|
||||
prompt = await Agent.prepare_prompt(agent, loop_data)
|
||||
|
||||
assert isinstance(prompt[0], SystemMessage)
|
||||
assert prompt[0].content == "System root."
|
||||
assert isinstance(prompt[1], HumanMessage)
|
||||
assert str(prompt[1].content).startswith("[PROTOCOL]")
|
||||
assert str(prompt[1].content).index("Project rule.") < str(prompt[1].content).index(
|
||||
"User asks."
|
||||
)
|
||||
assert isinstance(prompt[2], AIMessage)
|
||||
assert prompt[2].content == "Assistant answers."
|
||||
assert isinstance(prompt[3], HumanMessage)
|
||||
assert str(prompt[3].content).startswith("[EXTRAS]")
|
||||
assert "Today." in str(prompt[3].content)
|
||||
|
||||
serialized_history = agent.history.serialize()
|
||||
assert "Project rule." not in serialized_history
|
||||
assert "Today." not in serialized_history
|
||||
assert "protocol" not in serialized_history.lower()
|
||||
assert loop_data.protocol_temporary == {}
|
||||
assert loop_data.extras_temporary == {}
|
||||
|
||||
class FakeResponsesModel:
|
||||
def _convert_messages(self, messages):
|
||||
role_by_type = {"system": "system", "human": "user", "ai": "assistant"}
|
||||
return [
|
||||
{"role": role_by_type[message.type], "content": message.content}
|
||||
for message in messages
|
||||
]
|
||||
|
||||
input_items = Agent._responses_prompt_input_items(
|
||||
agent,
|
||||
FakeResponsesModel(),
|
||||
prompt,
|
||||
)
|
||||
assert input_items[1]["role"] == "user"
|
||||
assert "[PROTOCOL]" in input_items[1]["content"]
|
||||
assert "[EXTRAS]" in input_items[-1]["content"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_project_prompt_moves_project_instructions_to_protocol(monkeypatch):
|
||||
project_vars = {
|
||||
"project_name": "Demo",
|
||||
"project_description": "",
|
||||
"project_instructions": "Project rule.",
|
||||
"project_path": "/a0/usr/projects/demo",
|
||||
"project_git_url": "",
|
||||
}
|
||||
loop_data = LoopData()
|
||||
|
||||
class FakeContext:
|
||||
def get_data(self, key):
|
||||
assert key == project_prompt.projects.CONTEXT_DATA_KEY_PROJECT
|
||||
return "demo"
|
||||
|
||||
class FakeAgent:
|
||||
context = FakeContext()
|
||||
|
||||
def read_prompt(self, prompt_file: str, **kwargs) -> str:
|
||||
if prompt_file == "agent.system.projects.main.md":
|
||||
return "project context may be active"
|
||||
if prompt_file == "agent.system.projects.active.md":
|
||||
return f"active project: {kwargs['project_path']}"
|
||||
if prompt_file == "agent.protocol.projects.instructions.md":
|
||||
return "protocol project instructions:\n" + kwargs["project_instructions"]
|
||||
raise AssertionError(f"Unexpected prompt file: {prompt_file}")
|
||||
|
||||
monkeypatch.setattr(
|
||||
project_prompt.projects,
|
||||
"build_system_prompt_vars",
|
||||
lambda _name: project_vars,
|
||||
)
|
||||
|
||||
prompt = await project_prompt.build_prompt.__wrapped__( # type: ignore[attr-defined]
|
||||
FakeAgent(),
|
||||
loop_data=loop_data,
|
||||
)
|
||||
|
||||
assert "Project rule." not in prompt
|
||||
assert "Project rule." in loop_data.protocol_persistent["project_instructions"]
|
||||
|
|
@ -146,6 +146,24 @@ def test_skills_tool_accepts_action_alias_for_search(monkeypatch, tmp_path: Path
|
|||
assert "browser-form-workflows" in response.message
|
||||
|
||||
|
||||
def test_skills_tool_load_reports_protocol_injection(monkeypatch, tmp_path: Path):
|
||||
module = _load_skills_tool(monkeypatch, tmp_path)
|
||||
agent = _FakeAgent()
|
||||
tool = module.SkillsTool(
|
||||
agent,
|
||||
"skills_tool",
|
||||
None,
|
||||
{"action": "load", "skill_name": "browser-form-workflows"},
|
||||
"",
|
||||
None,
|
||||
)
|
||||
|
||||
response = asyncio.run(tool.execute(**tool.args))
|
||||
|
||||
assert response.message == "Loaded skill 'browser-form-workflows' into Protocol."
|
||||
assert agent.data["loaded_skills"] == ["browser-form-workflows"]
|
||||
|
||||
|
||||
def test_skills_tool_read_file_action_reads_inside_skill_dir(
|
||||
monkeypatch, tmp_path: Path
|
||||
):
|
||||
|
|
|
|||
|
|
@ -209,7 +209,7 @@ class SkillsTool(Tool):
|
|||
loaded.append(skill.name)
|
||||
self.agent.data[DATA_NAME_LOADED_SKILLS] = loaded[-max_loaded_skills():]
|
||||
|
||||
return f"Loaded skill '{skill.name}' into EXTRAS."
|
||||
return f"Loaded skill '{skill.name}' into Protocol."
|
||||
|
||||
def _read_file(self, skill_name: str, file_path: str) -> str:
|
||||
if not skill_name:
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@
|
|||
- Update this file whenever tool arguments, output shape, `break_loop` behavior, intervention handling, prompt instructions, or side effects change.
|
||||
- `SkillsTool` is a `Tool`.
|
||||
- `SkillsTool` defines `execute(...)`.
|
||||
- Loading a skill stores it for prompt Protocol injection on subsequent turns.
|
||||
- Observed side-effect areas: filesystem reads, filesystem deletion, settings/state persistence.
|
||||
- Imported dependency areas include: `__future__`, `helpers`, `helpers.print_style`, `helpers.tool`, `pathlib`, `typing`.
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue