agent-zero/plugins/_memory/tools/behaviour_adjustment.py
Alessandro daf95ec3ab Normalize tool contracts and slim prompt surface
Standardize multi-action tools around tool_args.action while keeping parser compatibility for older tool/args, tool_name:action, and method-shaped requests. This keeps new prompts clean without breaking agents that learned the previous dialect.

Move A0 connector remote execution/file tools into stable standard prompts, make remote targeting independent of the active chat context, and skill-gate beta computer-use remote so it no longer weighs down the always-on tool list.

Align text editor, scheduler, skills, office artifact, memory, notify, and browser prompts/tools around the canonical action contract. Add scheduler update/timezone handling, skills_tool read_file, text editor patch coverage, and fixes for memory_forget, behaviour_adjustment, and code execution progress warnings.

Reduce default prompt pressure by compacting browser and scheduler prompts into skill-backed manifests, shortening skill catalog descriptions, and pruning noisy framework knowledge. Remove obsolete connector prompt stubs and root tool-call knowledge examples.

Tests: conda run -n a0 pytest tests/test_a0_connector_prompt_gating.py tests/test_tool_action_contracts.py tests/test_task_scheduler_timezone.py tests/test_text_editor_context_patch.py tests/test_tool_request_normalization.py tests/test_office_document_store.py::test_odf_is_advertised_and_docx_remains_explicit_compatibility tests/test_office_document_store.py::test_document_artifact_accepts_method_alias_for_ods_create tests/test_skills_runtime.py tests/test_default_prompt_budget.py::test_a0_small_profile_removed_and_prompt_text_generic -q
2026-05-09 21:54:43 +02:00

100 lines
3 KiB
Python

from helpers import files
from helpers.tool import Tool, Response
from agent import Agent
from helpers.log import LogItem
from plugins._memory.helpers import memory
class UpdateBehaviour(Tool):
async def execute(self, adjustments="", **kwargs):
# stringify adjustments if needed
if not isinstance(adjustments, str):
adjustments = str(adjustments)
await update_behaviour(self.agent, self.log, adjustments)
return Response(
message=self.agent.read_prompt("behaviour.updated.md"), break_loop=False
)
async def update_behaviour(agent: Agent, log_item: LogItem, adjustments: str):
# get system message and current ruleset
system = agent.read_prompt("behaviour.merge.sys.md")
current_rules = read_rules(agent)
# log query streamed by LLM
async def log_callback(content):
log_item.stream(ruleset=content)
msg = agent.read_prompt(
"behaviour.merge.msg.md", current_rules=current_rules, adjustments=adjustments
)
# call util llm to find solutions in history
adjustments_merge = await agent.call_utility_model(
system=system,
message=msg,
callback=log_callback,
)
adjustments_merge = normalize_ruleset(adjustments_merge)
# update rules file
rules_file = get_custom_rules_file(agent)
files.write_file(rules_file, adjustments_merge)
log_item.update(ruleset=adjustments_merge, result="Behaviour updated")
def get_custom_rules_file(agent: Agent):
return files.get_abs_path(memory.get_memory_subdir_abs(agent), "behaviour.md")
def read_rules(agent: Agent):
rules_file = get_custom_rules_file(agent)
if files.exists(rules_file):
return agent.read_prompt(rules_file)
else:
return agent.read_prompt("agent.system.behaviour_default.md")
def normalize_ruleset(ruleset: str):
text = str(ruleset or "").strip()
if text.startswith("```") and text.endswith("```"):
lines = text.splitlines()
text = "\n".join(lines[1:-1]).strip()
text = text.replace("\r\n", "\n").replace("\r", "\n")
text = text.replace("!!!", "")
text = text.replace(".## ", ".\n## ")
normalized_lines = []
seen_structural_lines = set()
previous_blank = False
for raw_line in text.splitlines():
line = raw_line.rstrip()
stripped = line.strip()
if not stripped:
if normalized_lines and not previous_blank:
normalized_lines.append("")
previous_blank = True
continue
if stripped.startswith("# ") and not stripped.startswith("## "):
stripped = "#" + stripped
line = stripped
dedupe_key = stripped.casefold()
if stripped.startswith(("## ", "* ")) and dedupe_key in seen_structural_lines:
continue
if stripped.startswith(("## ", "* ")):
seen_structural_lines.add(dedupe_key)
normalized_lines.append(line)
previous_blank = False
return "\n".join(normalized_lines).strip() + "\n"