mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-25 00:51:20 +00:00
Local customizations: vault injection (Layer 3) preserved after upstream update
This commit is contained in:
parent
4fade39c90
commit
de1a3922ed
5 changed files with 495 additions and 2 deletions
117
agent/vault_injection.py
Normal file
117
agent/vault_injection.py
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
"""Vault injection — auto-load Obsidian vault files into the system prompt.
|
||||
|
||||
Reads working-context.md and user-profile.md from a configured vault path
|
||||
at session start and injects them into the system prompt alongside Layer 1
|
||||
memory (MEMORY.md / USER.md). This is a structural fix for vault neglect:
|
||||
the agent no longer needs to remember to read these files — they're injected
|
||||
automatically, the same way Layer 1 memory is.
|
||||
|
||||
The vault is Layer 3 in the memory architecture. Files injected here are
|
||||
read-only in the system prompt (frozen at session start). Mid-session
|
||||
writes to vault files require the read_file/write_file tools or the
|
||||
memory-vault skill.
|
||||
|
||||
Config (in config.yaml under 'vault'):
|
||||
enabled: true # enable vault injection
|
||||
path: /path/to/vault # absolute path to the Obsidian vault root
|
||||
|
||||
Files read (relative to vault path):
|
||||
Agent-Hermes/working-context.md — what the agent is actively doing
|
||||
Agent-Shared/user-profile.md — who the user is (durable facts)
|
||||
|
||||
If either file doesn't exist or is empty, it's silently skipped.
|
||||
If the vault path doesn't exist or isn't configured, vault injection is
|
||||
silently disabled.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Character limits for vault injection blocks (to prevent prompt bloat)
|
||||
WORKING_CONTEXT_CHAR_LIMIT = 4000
|
||||
USER_PROFILE_CHAR_LIMIT = 4000
|
||||
|
||||
SEPARATOR = "\u2550" * 46 # ═ same as memory_tool uses
|
||||
|
||||
|
||||
def _read_vault_file(path: Path, char_limit: int) -> Optional[str]:
|
||||
"""Read a vault file and return its content, or None if missing/empty.
|
||||
|
||||
Truncates with a notice if the file exceeds char_limit.
|
||||
"""
|
||||
if not path.exists():
|
||||
return None
|
||||
try:
|
||||
content = path.read_text(encoding="utf-8").strip()
|
||||
except (OSError, IOError) as e:
|
||||
logger.debug("Could not read vault file %s: %s", path, e)
|
||||
return None
|
||||
|
||||
if not content:
|
||||
return None
|
||||
|
||||
# Strip YAML frontmatter (same as prompt_builder does for context files)
|
||||
content = _strip_yaml_frontmatter(content)
|
||||
|
||||
if not content:
|
||||
return None
|
||||
|
||||
if len(content) > char_limit:
|
||||
truncated = content[:char_limit]
|
||||
# Find last newline to avoid cutting mid-line
|
||||
last_nl = truncated.rfind("\n")
|
||||
if last_nl > char_limit // 2:
|
||||
truncated = truncated[:last_nl]
|
||||
content = truncated + f"\n[... truncated at {char_limit} chars ...]"
|
||||
|
||||
return content
|
||||
|
||||
|
||||
def _strip_yaml_frontmatter(content: str) -> str:
|
||||
"""Remove optional YAML frontmatter (--- delimited) from content."""
|
||||
if content.startswith("---"):
|
||||
end = content.find("\n---", 3)
|
||||
if end != -1:
|
||||
body = content[end + 4:].lstrip("\n")
|
||||
return body if body else content
|
||||
return content
|
||||
|
||||
|
||||
def build_vault_system_prompt(vault_path: str) -> str:
|
||||
"""Build the vault injection block for the system prompt.
|
||||
|
||||
Reads working-context.md and user-profile.md from the vault and formats
|
||||
them with headers matching the style of Layer 1 memory blocks.
|
||||
|
||||
Returns an empty string if vault is disabled, path is missing, or
|
||||
all files are empty.
|
||||
"""
|
||||
if not vault_path:
|
||||
return ""
|
||||
|
||||
vault_root = Path(vault_path)
|
||||
if not vault_root.is_dir():
|
||||
logger.debug("Vault path does not exist or is not a directory: %s", vault_path)
|
||||
return ""
|
||||
|
||||
parts = []
|
||||
|
||||
# Read working-context.md (agent's current state)
|
||||
wc_path = vault_root / "Agent-Hermes" / "working-context.md"
|
||||
wc_content = _read_vault_file(wc_path, WORKING_CONTEXT_CHAR_LIMIT)
|
||||
if wc_content:
|
||||
header = "VAULT: WORKING CONTEXT (what you're doing right now)"
|
||||
parts.append(f"{SEPARATOR}\n{header}\n{SEPARATOR}\n{wc_content}")
|
||||
|
||||
# Read user-profile.md (shared user profile)
|
||||
up_path = vault_root / "Agent-Shared" / "user-profile.md"
|
||||
up_content = _read_vault_file(up_path, USER_PROFILE_CHAR_LIMIT)
|
||||
if up_content:
|
||||
header = "VAULT: USER PROFILE (durable facts from Obsidian vault)"
|
||||
parts.append(f"{SEPARATOR}\n{header}\n{SEPARATOR}\n{up_content}")
|
||||
|
||||
return "\n\n".join(parts)
|
||||
|
|
@ -754,6 +754,16 @@ DEFAULT_CONFIG = {
|
|||
"provider": "",
|
||||
},
|
||||
|
||||
# Obsidian vault auto-injection — Layer 3 persistent memory.
|
||||
# When enabled, working-context.md and user-profile.md are read from
|
||||
# the vault path at session start and injected into the system prompt
|
||||
# alongside Layer 1 memory. This is a structural fix for vault neglect:
|
||||
# the agent no longer needs to remember to read these files manually.
|
||||
"vault": {
|
||||
"enabled": False, # set true to activate
|
||||
"path": "", # absolute path to the Obsidian vault root
|
||||
},
|
||||
|
||||
# Subagent delegation — override the provider:model used by delegate_task
|
||||
# so child agents can run on a different (cheaper/faster) provider and model.
|
||||
# Uses the same runtime provider resolution as CLI/gateway startup, so all
|
||||
|
|
|
|||
35
run_agent.py
35
run_agent.py
|
|
@ -1566,7 +1566,8 @@ class AIAgent:
|
|||
try:
|
||||
from hermes_cli.config import load_config as _load_agent_config
|
||||
_agent_cfg = _load_agent_config()
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
logger.warning("Agent init: load_config() failed: %s: %s — using empty config", type(e).__name__, e)
|
||||
_agent_cfg = {}
|
||||
# Cache only the derived auxiliary compression context override that is
|
||||
# needed later by the startup feasibility check. Avoid exposing a
|
||||
|
|
@ -1597,8 +1598,20 @@ class AIAgent:
|
|||
self._memory_store.load_from_disk()
|
||||
except Exception:
|
||||
pass # Memory is optional -- don't break agent init
|
||||
|
||||
|
||||
# Obsidian vault auto-injection (Layer 3) — structural fix for
|
||||
# vault neglect. Reads working-context.md and user-profile.md
|
||||
# from the configured vault path and injects them into the system
|
||||
# prompt at session start, just like Layer 1 memory.
|
||||
self._vault_enabled = False
|
||||
self._vault_path = ""
|
||||
try:
|
||||
vault_config = _agent_cfg.get("vault", {})
|
||||
self._vault_enabled = vault_config.get("enabled", False)
|
||||
self._vault_path = vault_config.get("path", "")
|
||||
logging.getLogger("agent.vault").info("Vault config: enabled=%s path=%s", self._vault_enabled, self._vault_path)
|
||||
except Exception as e:
|
||||
logging.getLogger("agent.vault").warning("Vault config read failed: %s: %s", type(e).__name__, e)
|
||||
|
||||
# Memory provider plugin (external — one at a time, alongside built-in)
|
||||
# Reads memory.provider from config to select which plugin to activate.
|
||||
|
|
@ -4448,6 +4461,24 @@ class AIAgent:
|
|||
if user_block:
|
||||
prompt_parts.append(user_block)
|
||||
|
||||
# Vault auto-injection (Layer 3) — reads working-context.md and
|
||||
# user-profile.md from the Obsidian vault and injects them into
|
||||
# the system prompt. Structural fix for vault neglect.
|
||||
_vault_log = logging.getLogger("agent.vault")
|
||||
if self._vault_enabled and self._vault_path:
|
||||
try:
|
||||
from agent.vault_injection import build_vault_system_prompt
|
||||
_vault_block = build_vault_system_prompt(self._vault_path)
|
||||
if _vault_block:
|
||||
prompt_parts.append(_vault_block)
|
||||
_vault_log.info("Injection succeeded: %d chars from %s", len(_vault_block), self._vault_path)
|
||||
else:
|
||||
_vault_log.warning("Injection returned empty for path %s", self._vault_path)
|
||||
except Exception as e:
|
||||
_vault_log.warning("Injection failed: %s: %s", type(e).__name__, e)
|
||||
else:
|
||||
_vault_log.info("Injection skipped: enabled=%s path=%s", self._vault_enabled, self._vault_path)
|
||||
|
||||
# External memory provider system prompt block (additive to built-in)
|
||||
if self._memory_manager:
|
||||
try:
|
||||
|
|
|
|||
174
tests/agent/test_vault_injection.py
Normal file
174
tests/agent/test_vault_injection.py
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
"""Tests for agent/vault_injection.py — Obsidian vault auto-injection into system prompt."""
|
||||
|
||||
import pytest
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from agent.vault_injection import (
|
||||
build_vault_system_prompt,
|
||||
_read_vault_file,
|
||||
_strip_yaml_frontmatter,
|
||||
WORKING_CONTEXT_CHAR_LIMIT,
|
||||
USER_PROFILE_CHAR_LIMIT,
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# _strip_yaml_frontmatter
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestStripYamlFrontmatter:
|
||||
def test_strips_simple_frontmatter(self):
|
||||
content = "---\ndate: 2026-04-22\n---\nHello world"
|
||||
assert _strip_yaml_frontmatter(content) == "Hello world"
|
||||
|
||||
def test_no_frontmatter(self):
|
||||
content = "Just some text"
|
||||
assert _strip_yaml_frontmatter(content) == "Just some text"
|
||||
|
||||
def test_frontmatter_with_blank_lines(self):
|
||||
content = "---\ndate: 2026-04-22\nprojects: [X]\n---\n\nActual content here"
|
||||
result = _strip_yaml_frontmatter(content)
|
||||
assert result == "Actual content here"
|
||||
|
||||
def test_unclosed_frontmatter_returns_original(self):
|
||||
content = "---\ndate: 2026-04-22\nNo closing dashes"
|
||||
assert _strip_yaml_frontmatter(content) == content
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# _read_vault_file
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestReadVaultFile:
|
||||
def test_reads_existing_file(self, tmp_path):
|
||||
f = tmp_path / "test.md"
|
||||
f.write_text("some content", encoding="utf-8")
|
||||
result = _read_vault_file(f, 4000)
|
||||
assert result == "some content"
|
||||
|
||||
def test_returns_none_for_missing_file(self, tmp_path):
|
||||
f = tmp_path / "nonexistent.md"
|
||||
result = _read_vault_file(f, 4000)
|
||||
assert result is None
|
||||
|
||||
def test_returns_none_for_empty_file(self, tmp_path):
|
||||
f = tmp_path / "empty.md"
|
||||
f.write_text("", encoding="utf-8")
|
||||
result = _read_vault_file(f, 4000)
|
||||
assert result is None
|
||||
|
||||
def test_returns_none_for_whitespace_only(self, tmp_path):
|
||||
f = tmp_path / "ws.md"
|
||||
f.write_text(" \n\n ", encoding="utf-8")
|
||||
result = _read_vault_file(f, 4000)
|
||||
assert result is None
|
||||
|
||||
def test_strips_frontmatter(self, tmp_path):
|
||||
f = tmp_path / "frontmatter.md"
|
||||
f.write_text("---\ndate: 2026-04-22\n---\nReal content", encoding="utf-8")
|
||||
result = _read_vault_file(f, 4000)
|
||||
assert result == "Real content"
|
||||
|
||||
def test_truncates_long_file(self, tmp_path):
|
||||
f = tmp_path / "long.md"
|
||||
long_content = "x" * 5000
|
||||
f.write_text(long_content, encoding="utf-8")
|
||||
result = _read_vault_file(f, 100)
|
||||
assert len(result) < 200 # truncation + notice
|
||||
assert "truncated" in result
|
||||
|
||||
def test_truncation_at_newline(self, tmp_path):
|
||||
f = tmp_path / "multiline.md"
|
||||
lines = ["line " + str(i) for i in range(100)]
|
||||
content = "\n".join(lines)
|
||||
f.write_text(content, encoding="utf-8")
|
||||
# Small limit, should truncate at a newline boundary
|
||||
result = _read_vault_file(f, 50)
|
||||
assert "truncated" in result
|
||||
# Should not cut mid-line
|
||||
for line in result.split("\n"):
|
||||
if line and "truncated" not in line:
|
||||
assert line.startswith("line")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# build_vault_system_prompt
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestBuildVaultSystemPrompt:
|
||||
def test_empty_path_returns_empty(self):
|
||||
assert build_vault_system_prompt("") == ""
|
||||
|
||||
def test_nonexistent_path_returns_empty(self, tmp_path):
|
||||
assert build_vault_system_prompt(str(tmp_path / "nope")) == ""
|
||||
|
||||
def test_empty_vault_dir_returns_empty(self, tmp_path):
|
||||
assert build_vault_system_prompt(str(tmp_path)) == ""
|
||||
|
||||
def test_injects_working_context(self, tmp_path):
|
||||
vault = tmp_path / "vault"
|
||||
agent_dir = vault / "Agent-Hermes"
|
||||
agent_dir.mkdir(parents=True)
|
||||
wc = agent_dir / "working-context.md"
|
||||
wc.write_text("---\ndate: 2026-04-22\n---\n## Current Status\n- Status: Active", encoding="utf-8")
|
||||
|
||||
result = build_vault_system_prompt(str(vault))
|
||||
assert "VAULT: WORKING CONTEXT" in result
|
||||
assert "Status: Active" in result
|
||||
|
||||
def test_injects_user_profile(self, tmp_path):
|
||||
vault = tmp_path / "vault"
|
||||
shared_dir = vault / "Agent-Shared"
|
||||
shared_dir.mkdir(parents=True)
|
||||
up = shared_dir / "user-profile.md"
|
||||
up.write_text("# User Profile\n\nName: AJ", encoding="utf-8")
|
||||
|
||||
result = build_vault_system_prompt(str(vault))
|
||||
assert "VAULT: USER PROFILE" in result
|
||||
assert "Name: AJ" in result
|
||||
|
||||
def test_injects_both_files(self, tmp_path):
|
||||
vault = tmp_path / "vault"
|
||||
agent_dir = vault / "Agent-Hermes"
|
||||
shared_dir = vault / "Agent-Shared"
|
||||
agent_dir.mkdir(parents=True)
|
||||
shared_dir.mkdir(parents=True)
|
||||
|
||||
(agent_dir / "working-context.md").write_text(
|
||||
"---\ndate: 2026-04-22\n---\nWorking on X", encoding="utf-8"
|
||||
)
|
||||
(shared_dir / "user-profile.md").write_text(
|
||||
"# User Profile\n\nName: AJ", encoding="utf-8"
|
||||
)
|
||||
|
||||
result = build_vault_system_prompt(str(vault))
|
||||
assert "VAULT: WORKING CONTEXT" in result
|
||||
assert "VAULT: USER PROFILE" in result
|
||||
assert "Working on X" in result
|
||||
assert "Name: AJ" in result
|
||||
|
||||
def test_skips_empty_working_context(self, tmp_path):
|
||||
vault = tmp_path / "vault"
|
||||
agent_dir = vault / "Agent-Hermes"
|
||||
shared_dir = vault / "Agent-Shared"
|
||||
agent_dir.mkdir(parents=True)
|
||||
shared_dir.mkdir(parents=True)
|
||||
|
||||
(agent_dir / "working-context.md").write_text("", encoding="utf-8")
|
||||
(shared_dir / "user-profile.md").write_text("Name: AJ", encoding="utf-8")
|
||||
|
||||
result = build_vault_system_prompt(str(vault))
|
||||
assert "VAULT: WORKING CONTEXT" not in result
|
||||
assert "VAULT: USER PROFILE" in result
|
||||
|
||||
def test_format_matches_memory_block_style(self, tmp_path):
|
||||
vault = tmp_path / "vault"
|
||||
agent_dir = vault / "Agent-Hermes"
|
||||
agent_dir.mkdir(parents=True)
|
||||
(agent_dir / "working-context.md").write_text("Active task", encoding="utf-8")
|
||||
|
||||
result = build_vault_system_prompt(str(vault))
|
||||
# Should use the same separator as memory_tool (═══)
|
||||
assert "\u2550" in result # ═ character
|
||||
assert "VAULT: WORKING CONTEXT" in result
|
||||
161
tests/run_agent/test_vault_injection.py
Normal file
161
tests/run_agent/test_vault_injection.py
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
"""Tests for vault auto-injection integration with _build_system_prompt.
|
||||
|
||||
Verifies that vault content appears in the system prompt when vault is
|
||||
configured, and is absent otherwise.
|
||||
"""
|
||||
|
||||
import os
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
|
||||
def _make_minimal_agent(**overrides):
|
||||
"""Create a minimal AIAgent for testing, with vault attrs settable."""
|
||||
from run_agent import AIAgent
|
||||
|
||||
with (
|
||||
patch("run_agent.get_tool_definitions", return_value=[]),
|
||||
patch("run_agent.check_toolset_requirements", return_value={}),
|
||||
patch("run_agent.OpenAI"),
|
||||
):
|
||||
a = AIAgent(
|
||||
api_key="test-k...7890",
|
||||
base_url="https://openrouter.ai/api/v1",
|
||||
quiet_mode=True,
|
||||
skip_context_files=True,
|
||||
skip_memory=True,
|
||||
)
|
||||
a.client = MagicMock()
|
||||
|
||||
# Apply overrides after creation
|
||||
for k, v in overrides.items():
|
||||
setattr(a, k, v)
|
||||
|
||||
return a
|
||||
|
||||
|
||||
class TestVaultSystemPromptIntegration:
|
||||
"""Test that _build_system_prompt injects vault content when configured."""
|
||||
|
||||
def test_vault_not_injected_when_disabled(self, tmp_path):
|
||||
"""Vault content should not appear when vault_enabled=False."""
|
||||
vault_dir = tmp_path / "vault"
|
||||
agent_dir = vault_dir / "Agent-Hermes"
|
||||
agent_dir.mkdir(parents=True)
|
||||
(agent_dir / "working-context.md").write_text("Active task X", encoding="utf-8")
|
||||
|
||||
agent = _make_minimal_agent(
|
||||
_vault_enabled=False,
|
||||
_vault_path=str(vault_dir),
|
||||
)
|
||||
|
||||
prompt = agent._build_system_prompt()
|
||||
assert "VAULT: WORKING CONTEXT" not in prompt
|
||||
assert "Active task X" not in prompt
|
||||
|
||||
def test_vault_injected_when_enabled(self, tmp_path):
|
||||
"""Vault content should appear in system prompt when vault_enabled=True."""
|
||||
vault_dir = tmp_path / "vault"
|
||||
agent_dir = vault_dir / "Agent-Hermes"
|
||||
shared_dir = vault_dir / "Agent-Shared"
|
||||
agent_dir.mkdir(parents=True)
|
||||
shared_dir.mkdir(parents=True)
|
||||
(agent_dir / "working-context.md").write_text(
|
||||
"---\ndate: 2026-04-22\n---\n## Status\nActive: vault fix",
|
||||
encoding="utf-8",
|
||||
)
|
||||
(shared_dir / "user-profile.md").write_text(
|
||||
"# User Profile\n\nName: Test User",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
agent = _make_minimal_agent(
|
||||
_vault_enabled=True,
|
||||
_vault_path=str(vault_dir),
|
||||
)
|
||||
|
||||
prompt = agent._build_system_prompt()
|
||||
assert "VAULT: WORKING CONTEXT" in prompt
|
||||
assert "Active: vault fix" in prompt
|
||||
assert "VAULT: USER PROFILE" in prompt
|
||||
assert "Name: Test User" in prompt
|
||||
|
||||
def test_vault_after_memory_blocks(self, tmp_path):
|
||||
"""Vault injection should appear after Layer 1 memory blocks."""
|
||||
# Set up memory files
|
||||
mem_dir = tmp_path / "memories"
|
||||
mem_dir.mkdir(parents=True)
|
||||
(mem_dir / "MEMORY.md").write_text("Layer 1 memory note", encoding="utf-8")
|
||||
|
||||
# Set up vault files
|
||||
vault_dir = tmp_path / "vault"
|
||||
agent_dir = vault_dir / "Agent-Hermes"
|
||||
agent_dir.mkdir(parents=True)
|
||||
(agent_dir / "working-context.md").write_text("Vault content", encoding="utf-8")
|
||||
|
||||
# Create agent with memory enabled
|
||||
from run_agent import AIAgent
|
||||
from tools.memory_tool import MemoryStore
|
||||
|
||||
with (
|
||||
patch("run_agent.get_tool_definitions", return_value=[]),
|
||||
patch("run_agent.check_toolset_requirements", return_value={}),
|
||||
patch("run_agent.OpenAI"),
|
||||
patch(
|
||||
"hermes_cli.config.load_config",
|
||||
return_value={
|
||||
"memory": {
|
||||
"memory_enabled": True,
|
||||
"user_profile_enabled": False,
|
||||
"memory_char_limit": 2200,
|
||||
"user_char_limit": 1375,
|
||||
},
|
||||
},
|
||||
),
|
||||
):
|
||||
monkeypatch_env = {}
|
||||
# Set HERMES_HOME so MemoryStore reads from tmp_path
|
||||
os.environ["HERMES_HOME"] = str(tmp_path)
|
||||
try:
|
||||
a = AIAgent(
|
||||
api_key="test-k...7890",
|
||||
base_url="https://openrouter.ai/api/v1",
|
||||
quiet_mode=True,
|
||||
skip_context_files=True,
|
||||
skip_memory=False,
|
||||
)
|
||||
a.client = MagicMock()
|
||||
finally:
|
||||
del os.environ["HERMES_HOME"]
|
||||
|
||||
a._vault_enabled = True
|
||||
a._vault_path = str(vault_dir)
|
||||
|
||||
prompt = a._build_system_prompt()
|
||||
mem_pos = prompt.find("MEMORY (your personal notes)")
|
||||
vault_pos = prompt.find("VAULT: WORKING CONTEXT")
|
||||
assert mem_pos > 0, "Layer 1 memory block not found in prompt"
|
||||
assert vault_pos > 0, "Vault block not found in prompt"
|
||||
assert mem_pos < vault_pos, "Vault should appear after Layer 1 memory"
|
||||
|
||||
def test_missing_vault_path_graceful(self, tmp_path):
|
||||
"""Agent works fine even if vault path doesn't exist."""
|
||||
agent = _make_minimal_agent(
|
||||
_vault_enabled=True,
|
||||
_vault_path="/nonexistent/vault/path",
|
||||
)
|
||||
|
||||
# Should not crash
|
||||
prompt = agent._build_system_prompt()
|
||||
assert "VAULT:" not in prompt
|
||||
|
||||
def test_no_vault_config_graceful(self):
|
||||
"""Agent works fine with no vault set (defaults)."""
|
||||
agent = _make_minimal_agent(
|
||||
_vault_enabled=False,
|
||||
_vault_path="",
|
||||
)
|
||||
|
||||
prompt = agent._build_system_prompt()
|
||||
assert "VAULT:" not in prompt
|
||||
Loading…
Add table
Add a link
Reference in a new issue