Import AITURK IDE 1.0.0-beta.1 from Hermes 63279301; preserve MIT license
This commit is contained in:
@@ -0,0 +1,175 @@
|
||||
"""Regression: multiplex gateway profile scoping + full-prompt wiring.
|
||||
|
||||
Two scenarios for _agent_home's resolution order (#86313 post-merge findings):
|
||||
|
||||
1. MULTIPLEX INVERSION (@kshitijk4poor): the messaging gateway hands every
|
||||
agent the shared launch-home state.db but binds the profile home per turn
|
||||
via the HERMES_HOME ContextVar (copy_context into the worker). A bound
|
||||
override must WIN over the db-derived launch home, else the shared-db
|
||||
fallback stomps the correct profile deterministically.
|
||||
|
||||
2. BARE THREAD (the original #86313 fix): no override bound — the db-derived
|
||||
home must still win over ambient env resolution.
|
||||
|
||||
Plus the full-prompt wiring test (@helix4u): build_system_prompt_parts on a
|
||||
bare thread with the bot's session DB must produce a prompt whose identity
|
||||
(SOUL.md), skills block, and profile line ALL belong to the bot — reverting
|
||||
any single call-site wire breaks this test.
|
||||
"""
|
||||
|
||||
import re
|
||||
import threading
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import patch
|
||||
|
||||
from hermes_constants import reset_hermes_home_override, set_hermes_home_override
|
||||
|
||||
|
||||
class _DB:
|
||||
def __init__(self, home: Path):
|
||||
self.db_path = home / "state.db"
|
||||
|
||||
|
||||
def _agent_for(home: Path, **overrides):
|
||||
base = dict(
|
||||
load_soul_identity=True,
|
||||
skip_context_files=True,
|
||||
valid_tool_names=[],
|
||||
_task_completion_guidance=False,
|
||||
_tool_use_enforcement=False,
|
||||
_environment_probe=False,
|
||||
_kanban_worker_guidance="",
|
||||
_memory_store=None,
|
||||
_memory_manager=None,
|
||||
model="",
|
||||
provider="",
|
||||
platform="",
|
||||
pass_session_id=False,
|
||||
session_id="",
|
||||
_session_db=_DB(home),
|
||||
)
|
||||
base.update(overrides)
|
||||
return SimpleNamespace(**base)
|
||||
|
||||
|
||||
def test_bound_override_wins_over_shared_db_home(tmp_path, monkeypatch):
|
||||
"""Multiplex lane: shared launch-home DB + per-turn ContextVar binding.
|
||||
The override must win, not the db-derived launch home."""
|
||||
from agent import system_prompt
|
||||
|
||||
root = tmp_path / "root"
|
||||
root.mkdir()
|
||||
bot_home = root / "profiles" / "mybot"
|
||||
bot_home.mkdir(parents=True)
|
||||
monkeypatch.setenv("HERMES_HOME", str(root))
|
||||
|
||||
agent = _agent_for(root) # shared db lives at <root>/state.db
|
||||
token = set_hermes_home_override(str(bot_home))
|
||||
try:
|
||||
assert system_prompt._agent_home(agent) == bot_home
|
||||
assert (
|
||||
system_prompt._profile_name_for_home(system_prompt._agent_home(agent))
|
||||
== "mybot"
|
||||
)
|
||||
finally:
|
||||
reset_hermes_home_override(token)
|
||||
|
||||
|
||||
def test_db_home_wins_on_bare_thread_without_override(tmp_path, monkeypatch):
|
||||
"""Original #86313 scenario: unbound thread, dedicated per-profile DB."""
|
||||
from agent import system_prompt
|
||||
|
||||
root = tmp_path / "root"
|
||||
bot_home = root / "profiles" / "mybot"
|
||||
bot_home.mkdir(parents=True)
|
||||
monkeypatch.setenv("HERMES_HOME", str(root))
|
||||
|
||||
agent = _agent_for(bot_home)
|
||||
result = {}
|
||||
|
||||
def resolve():
|
||||
result["home"] = system_prompt._agent_home(agent)
|
||||
|
||||
t = threading.Thread(target=resolve)
|
||||
t.start()
|
||||
t.join()
|
||||
|
||||
assert result["home"] == bot_home
|
||||
|
||||
|
||||
def test_full_prompt_scoped_to_bot_on_bare_thread(tmp_path, monkeypatch):
|
||||
"""Wiring test: SOUL.md identity, skills block, and profile line must ALL
|
||||
come from the bot's home when building on an unbound thread with the
|
||||
bot's session DB — no mixed-profile prompt."""
|
||||
from agent import prompt_builder
|
||||
from agent.system_prompt import build_system_prompt
|
||||
|
||||
default_home = tmp_path / "root"
|
||||
default_skills = default_home / "skills" / "general" / "leaky-skill"
|
||||
default_skills.mkdir(parents=True)
|
||||
(default_skills / "SKILL.md").write_text(
|
||||
"---\nname: leaky-skill\ndescription: default-only skill\n---\nbody\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
(default_home / "SOUL.md").write_text("DEFAULT SOUL", encoding="utf-8")
|
||||
|
||||
bot_home = default_home / "profiles" / "mybot"
|
||||
bot_skills = bot_home / "skills" / "general" / "bot-skill"
|
||||
bot_skills.mkdir(parents=True)
|
||||
(bot_skills / "SKILL.md").write_text(
|
||||
"---\nname: bot-skill\ndescription: bot-only skill\n---\nbody\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
(bot_home / "SOUL.md").write_text("BOT SOUL", encoding="utf-8")
|
||||
|
||||
# Ambient env resolves to the launch/default home; nothing binds the
|
||||
# ContextVar on the build thread.
|
||||
monkeypatch.setenv("HERMES_HOME", str(default_home))
|
||||
prompt_builder.clear_skills_system_prompt_cache(clear_snapshot=False)
|
||||
|
||||
agent = _agent_for(bot_home, valid_tool_names=["skill_view"])
|
||||
result = {}
|
||||
|
||||
def build():
|
||||
with (
|
||||
patch("run_agent.build_environment_hints", return_value=""),
|
||||
):
|
||||
result["prompt"] = build_system_prompt(agent)
|
||||
|
||||
t = threading.Thread(target=build)
|
||||
t.start()
|
||||
t.join()
|
||||
prompt = result["prompt"]
|
||||
|
||||
assert "BOT SOUL" in prompt
|
||||
assert "DEFAULT SOUL" not in prompt
|
||||
m = re.search(r"<available_skills>(.*?)</available_skills>", prompt, re.DOTALL)
|
||||
skills_block = m.group(1) if m else ""
|
||||
assert "bot-skill" in skills_block
|
||||
assert "leaky-skill" not in skills_block
|
||||
assert "Active Hermes profile: mybot" in prompt
|
||||
assert "Active Hermes profile: default" not in prompt
|
||||
|
||||
|
||||
def test_plugin_session_info_profile_from_agent_home(tmp_path, monkeypatch):
|
||||
"""Plugin prompt metadata must carry the agent's own profile name, not the
|
||||
ambient one (@helix4u's plugin half)."""
|
||||
from agent import system_prompt
|
||||
|
||||
root = tmp_path / "root"
|
||||
bot_home = root / "profiles" / "mybot"
|
||||
bot_home.mkdir(parents=True)
|
||||
monkeypatch.setenv("HERMES_HOME", str(root))
|
||||
|
||||
agent = _agent_for(bot_home)
|
||||
result = {}
|
||||
|
||||
def resolve():
|
||||
result["info"] = system_prompt._plugin_session_info(agent)
|
||||
|
||||
t = threading.Thread(target=resolve)
|
||||
t.start()
|
||||
t.join()
|
||||
|
||||
assert result["info"]["profile_name"] == "mybot"
|
||||
Reference in New Issue
Block a user