Enterprise Prompt System (Current Architecture)
Audience: Engineers / operators who need a full mental model of how prompts are built and used end-to-end.
Scope: Current production architecture in this repo (LangChain agent loop + protocol routing + DB-backed prompt templates).
Primary code paths: backend/src/services/aso.service.ts, backend/src/services/promptComposition.service.ts, backend/src/services/aso/promptBuilder.service.ts, backend/src/services/langchain/*.
Why this exists (enterprise goals)
This prompt system is designed for separation of concerns and operational safety:
- Consistency: stable “System Core” rules + protocol-specific rules applied predictably.
- Modularity: persona/game/memory/attachments are injected as runtime modules, not mixed with governance.
- Security: explicit trust boundaries + injection defenses; untrusted context is treated as data-only.
- Maintainability: prompt bases live in DB, protocol overrides live in protocol definitions, and runtime assembly happens in code.
- Scalability: prompts can be versioned and rolled out per protocol with migrations.
Mental model: Prompt = layers + modules (not one mega-block)
Layering contract (v2)
When using base_tool_call_v2, the “system prompt” the model receives is logically:
- System Core (static rules)
- Protocol Rules (per protocol)
- User’s latest message (as the actual user message in the chat)
- Runtime Directives (per-turn orchestration hints)
- Runtime Context (data-only, explicitly untrusted)
- Available tools list (textual list; tool schemas are separately bound to the agent)
The literal template lives in:
backend/migrations/assets/base_tool_call_v2.txt(source of truth for migrations)backend/src/config/prompts.json(fallback if DB template missing)
Trust boundaries (critical)
Trusted inputs
- System Core rules (base template text)
- Protocol Rules (protocol definition overrides)
- Tool schemas bound to LangChain (code-defined
zodschemas) - Backend policy enforcement (tool access + protocol gating)
Untrusted inputs (data-only)
These are included for grounding, but must be treated as non-authoritative and non-instructional:
- Attachments metadata and extracted text (
injectedFileContext) - Long-term memory (
worldKnowledge,asoCoreMemories,sharedMemories) - “Familiarity graph” output (compact summary derived from user metadata)
- Tool outputs
- Web content (
web_fetch/web_searchresults)
base_tool_call_v2 includes explicit injection defenses and an instruction priority order.
Prompt sources (where templates come from)
1) DB-backed prompt templates (primary)
- Table:
prompt_templates - Loader:
backend/src/services/prompt.service.ts→getPromptTemplate(name) - Cache: in-memory map per process
2) JSON fallback templates (bootstrap only)
- File:
backend/src/config/prompts.json - Used if DB is missing a template or DB lookup fails.
3) Protocol definitions (rules + tool allowlists + base selection)
- Table:
protocols - Definition type:
backend/src/config/protocols/types.ts - Protocols typically reference a base template and provide overrides:
{
"promptTemplate": {
"base": "base_tool_call_v2",
"overrides": {
"rules": "=== TECHNICAL PROTOCOL RULES ===\n- ..."
}
}
}End-to-end runtime flow (current system)
This is the actual high-level execution path for a single chat turn.
Step-by-step explanation (what each stage does)
Step 1 — Protocol routing (choose “rules + tool surface”)
Goal: Decide which protocol applies (public/tech/neutral/private/system), and optionally merge with workflow protocol.
- Service:
backend/src/services/protocolRouter.service.ts - Result:
ProtocolPromptContext(protocolId/baseProtocolId/protocolDefinition/vows/phases/etc.)
Enterprise relevance:
- This is where you prevent “private identity-lore bleed” into public chats.
- Protocol also gates tools (observer tools restricted to system/private).
Step 2 — Context gather (runtime data assembly)
Goal: Prepare the data modules that may be injected into the prompt.
- Service:
backend/src/services/aso/context.service.tsgatherFullContext()collects:- Player state (persona/emotions/relationship flags)
- Quests + inventory
- 3-tier memory context (world/aso/personal)
- (CSO/system protocol only) system-wide knowledge
Enterprise relevance:
- Context is bounded/clamped and (for 1-on-1 with structured messages) avoids duplicating conversation transcripts.
- ASO-tier memory is restricted by protocol to avoid identity bleed.
Step 3 — Template composition (static base + protocol overrides)
Goal: Produce the template string that will later receive variables.
- Service:
backend/src/services/promptComposition.service.ts- Loads base prompt template by name
- Injects
(full or minimal) - Injects protocol rules:
- Prefer
placeholder (v2) - Otherwise append (legacy bases)
- Prefer
Enterprise relevance:
- This is where you guarantee protocol rules are not lost due to downstream stripping behavior.
Step 4 — Prompt build (fill variables + assemble layered modules)
Goal: Create the final toolCallPrompt string from template + runtime variables.
- Service:
backend/src/services/aso/promptBuilder.service.ts- Fills core variables (persona/emotions/memory/attachments/game state)
- Adds runtime directives (group chat policy, image analysis hint, proactive suggestions)
- Adds runtime context as data-only blocks when the template supports it (
base_tool_call_v2)
Relationship flags + familiarity graph
- Relationship flags injection: module-controlled:
minimal: omittedstandard: filtered summaryfull: full JSON
- Familiarity graph:
formatUserGraphForPrompt()creates a compact summary (top interests/projects/goals/tools/boundaries, etc.)- Injected into
sharedMemoriesand then summarized/clamped for non-group chat
Module mode control (protocol-controlled):
Add one of these to protocol.definition.scope[]:
prompt_modules:minimalprompt_modules:standardprompt_modules:full
Fine-grained toggles (optional, additive to the mode):
prompt_modules:game_state_offprompt_modules:familiarity_graph_offprompt_modules:external_profiles_off
Defaults if not specified:
- public/tech: minimal
- neutral: standard
- private/system: full
Step 5 — LangChain prompt derivation (system vs user)
Goal: Ensure the model sees:
full context as system prompt
real user message as user prompt
File:
backend/src/services/langchain/chains/promptRunnable.ts- Strips the
OUTPUT FORMAT:block while preserving trailing blocks - Ensures system-wide knowledge blocks aren’t accidentally dropped
- Strips the
Enterprise relevance:
- Prevents “prompt rules lost due to strip marker” regressions.
Step 6 — Tool binding + execution (hard enforcement in code)
Goal: The agent can only call tools that are allowed by:
- protocol allowlist
- runtime enablement overrides
- access policy (target scope + allowed protocols)
Key files:
- Tool compilation:
backend/src/services/tools/toolCompilation.service.ts - Tool schemas:
backend/src/services/tools/toolSchemas.ts - Agent loop:
backend/src/services/langchain/langchainToolTurn.service.ts - Execution + policy enforcement:
backend/src/services/toolExecutor.service.ts
Enterprise relevance:
- Prompt text is not the security boundary. Code is.
Operational controls (what operators can change)
Environment flags
PROMPT_TOOL_DEFS_MODE=minimal
Use minimal textual tool definitions in the prompt (token reduction).PROMPT_INCLUDE_COGNITIVE_NOTES=true
(Debug only) re-inject perception/salience text into the prompt. Default is off.PROMPT_RUNTIME_DIRECTIVES_MAX_CHARS(default2500)
Caps the per-turnblock (text).PROMPT_RUNTIME_CONTEXT_MAX_CHARS(default12000)
Caps the per-turnblock. For layered templates this remains valid JSON (it truncates fields safely or falls back to{ meta, truncated: true }).PROMPT_RUNTIME_CONTEXT_BUDGETS_JSON(advanced)
Optional per-field budgets for the JSON runtime context (percentages (\le 1.0) or absolute chars (> 1)).PROMPT_STRUCTURED_RUNTIME_CONTEXT_CHANNEL=system_message(experimental)
Emits the runtime context JSON as a separate system message (data-only). Can optionally redact the JSON blob inside the main system prompt content.PROMPT_STRUCTURED_RUNTIME_CONTEXT_REDACT_IN_SYSTEM=false
When the side-channel is enabled, keep the JSON blob in the main system prompt too (default is to redact).
Rollout mechanics (versioned and reversible)
Prompt bases are rolled out by:
- adding/updating prompt_templates entries via migrations
- switching a protocol’s
promptTemplate.basevia migrations
Example rollout file:
backend/migrations/20260224000040-switch-tech-protocol-to-base-tool-call-v2.js
Debugging / verification checklist
1) Admin prompt preview endpoint
POST /api/admin/prompts/preview(admin-only)- Route:
backend/src/routes/admin.routes.ts - Controller:
backend/src/controllers/promptDebug.controller.ts
Use this to verify:
- which base template is used (
templateName) - whether layered placeholders are active (
usesLayeredBlocks) - size deltas (template chars / filled chars)
2) DB checks
prompt_templatescontainsbase_tool_call_v2- relevant protocol points to
base_tool_call_v2
3) Tests
- Prompt stripping behavior regression:
backend/src/tests/promptRunnable.strip.jest.test.ts
- Layered runtime context + module toggle regression:
backend/src/tests/promptBuilder.runtimeContext.jest.test.ts
4) LangSmith
In LangSmith traces, verify:
- the system prompt begins with the layered
SYSTEM COREsection - protocol rules appear under
=== PROTOCOL RULES === - runtime data appears only under
=== RUNTIME CONTEXT (UNTRUSTED JSON DATA...) ===(a compact JSON object) - tools invoked match
compileAllowedToolIds()