feat: load operator instructions from agents/*.md into the reply prompt
Some checks failed
Release / semantic-release (push) Successful in 30s
Release / build-windows (push) Has been cancelled
Release / build-macos (arm64, macos-latest) (push) Has been cancelled
Release / build-macos (x64, macos-15-intel) (push) Has been cancelled
Release / build-linux (push) Has been cancelled
Release / release-main (push) Has been cancelled
Release / release-develop (push) Has been cancelled
tests / Unit tests (Linux, Python 3.11) (push) Has been cancelled

Drop Markdown files into an agents/ folder and their contents are appended to
the main reply LLM's system prompt, so an operator can extend the assistant's
rules/tone without code changes. Files are concatenated in filename order
(use 00-, 10- prefixes to control ordering) and re-read once per turn, so edits
apply on the next reply with no rebuild/restart. Fail-open: a missing, empty,
or unreadable folder yields no instructions and never breaks a reply.

- load_agent_instructions() in system_prompt.py (AGENTS_DIR env, default
  /app/agents); reads *.md only, skips blanks, ignores non-dir paths
- engine.py appends it alongside the existing settings-UI llm_instructions,
  under the same "Additional instructions from the operator:" framing
- docker-compose.yml bind-mounts ./agents:/app/agents:ro and sets AGENTS_DIR
- agents/example.md.sample starter template (.sample is not loaded)
- tests cover ordering, md-only filtering, blank-skip, env/arg resolution,
  and fail-open paths
- README, .env.example, docs/llm_contexts.md updated

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
javis-bot
2026-06-23 00:57:54 +09:00
parent 677bfcd2a9
commit 2f000ac6c8
8 changed files with 156 additions and 1 deletions

View File

@@ -6,8 +6,51 @@ who renames the wake word (e.g. "Friday") gets a butler with the matching
name rather than a persona hardcoded to "Jarvis".
"""
import os
from pathlib import Path
from typing import Optional
# Default location of the operator's file-based instruction folder. In the
# Docker deployment ./agents is bind-mounted here (see docker-compose.yml), so a
# user can drop *.md files in without rebuilding. Overridable via AGENTS_DIR.
_DEFAULT_AGENTS_DIR = "/app/agents"
def load_agent_instructions(agents_dir: Optional[str] = None) -> str:
"""Concatenate every ``*.md`` in the agents dir into one instruction block.
Files are read in filename order (so ``00-tone.md`` precedes ``10-rules.md``)
and joined with blank lines. This lets the operator extend the main reply
LLM's system prompt by dropping Markdown files into a folder, no code change
or restart required — the caller reads this once per turn.
Resolution order for the directory: explicit ``agents_dir`` arg, then the
``AGENTS_DIR`` env var, then ``/app/agents``.
Fail-open by design: a missing or empty directory, an unreadable file, or
any unexpected error yields ``""`` so a misconfigured folder can never break
a reply. Only regular ``*.md`` files are read; other files are ignored.
"""
directory = agents_dir or os.environ.get("AGENTS_DIR") or _DEFAULT_AGENTS_DIR
try:
base = Path(directory)
if not base.is_dir():
return ""
parts: list[str] = []
for path in sorted(base.glob("*.md"), key=lambda p: p.name):
if not path.is_file():
continue
try:
text = path.read_text(encoding="utf-8").strip()
except Exception:
continue
if text:
parts.append(text)
return "\n\n".join(parts).strip()
except Exception:
return ""
_SYSTEM_PROMPT_TEMPLATE: str = (
"Persona: you are a British butler named {name} — polite, composed, quietly amused, and "
"quietly enjoying yourself. Default voice is dry, witty, and lightly sarcastic: you notice "