Prompt Management System – Dynamic Loading and Custom Prompts
Running multiple sessions in Claude Control quickly makes it clear that prompts need to be managed systematically. This post covers building a system that separates system prompts, task-specific prompts, and shared instructions into discrete files and loads them dynamically.
Prompt Directory Structure
prompts/
├── system/
│ ├── default.md # 기본 시스템 프롬프트
│ ├── code_review.md # 코드 리뷰 전용
│ └── refactoring.md # 리팩토링 전용
├── tasks/
│ ├── frontend.md # 프론트엔드 작업 지시
│ ├── backend.md # 백엔드 작업 지시
│ └── testing.md # 테스트 작업 지시
└── templates/
├── bug_fix.md # 버그 수정 템플릿
└── feature_add.md # 기능 추가 템플릿
Implementing the Prompt Loader
from pathlib import Path
from string import Template
from typing import Dict, Optional
import yaml
class PromptManager:
def __init__(self, prompts_dir: str):
self.base_dir = Path(prompts_dir)
self._cache: Dict[str, str] = {}
self._metadata: Dict[str, dict] = {}
def load_prompt(self, path: str, variables: dict = None) -> str:
cache_key = f"{path}:{hash(frozenset((variables or {}).items()))}"
if cache_key in self._cache:
return self._cache[cache_key]
file_path = self.base_dir / path
if not file_path.exists():
raise FileNotFoundError(f"프롬프트 파일 없음: {path}")
content = file_path.read_text(encoding="utf-8")
# YAML 프론트매터 파싱
if content.startswith("---"):
parts = content.split("---", 2)
if len(parts) >= 3:
self._metadata[path] = yaml.safe_load(parts[1])
content = parts[2].strip()
# 변수 치환
if variables:
content = Template(content).safe_substitute(variables)
self._cache[cache_key] = content
return content
def get_system_prompt(self, role: str = "default") -> str:
return self.load_prompt(f"system/{role}.md")
def render_template(self, template_name: str, **kwargs) -> str:
return self.load_prompt(f"templates/{template_name}.md", kwargs)
Per-Session Prompt Configuration
Each session can be configured with a different system prompt.
class SessionPromptConfig:
def __init__(self, prompt_manager: PromptManager):
self.pm = prompt_manager
async def configure_session(self, session_id: str, config: dict):
system_prompt = self.pm.get_system_prompt(
config.get("role", "default")
)
task_prompt = ""
if "task_type" in config:
task_prompt = self.pm.load_prompt(
f"tasks/{config['task_type']}.md"
)
combined = f"{system_prompt}\n\n{task_prompt}"
await self._apply_to_session(session_id, combined)
Hot Reloading
The system also automatically invalidates the cache whenever a prompt file changes, using the watchdog library.
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
class PromptWatcher(FileSystemEventHandler):
def __init__(self, prompt_manager: PromptManager):
self.pm = prompt_manager
def on_modified(self, event):
if event.src_path.endswith(".md"):
self.pm._cache.clear()
print(f"프롬프트 캐시 갱신: {event.src_path}")
Wrapping Up
Moving prompt management to a file-system-based layout unlocks version control. You can track prompt change history with Git and share prompts with teammates without friction. The dynamic loading is particularly useful — prompts can be updated without restarting the server.