Prompt System Overview
Seven prompts drive every intelligent behavior. They live as string templates inside the two LLM service modules, follow shared structural conventions, and are catalogued verbatim in these docs.
Where prompts live
| Module | Prompts |
|---|---|
services/openai_service.py | agent chat wrapper · email analyzer · RAG answer · retrieval evaluator |
services/multi_agent_email_service.py | reply agent · reviewer agent |
There is no prompt registry, versioning, or template engine — prompts are Python f-strings/literals, versioned by git like any other code. That is a deliberate stage-appropriate choice; a managed prompt store is roadmap territory.
The two prompt species
1 · Composed prompts (runtime data woven in)
The chat and RAG-answer prompts are wrappers around agent configuration — name, system_prompt, language, tone are interpolated per request:
You are {agent_name}.
Agent instructions:
{system_prompt} ← the user-authored part
Language: {language}
Tone: {tone}
Rules: ... ← platform-enforced guardrailsThe user authors only the core; the platform supplies identity framing and non-negotiable rules ("do not invent facts", "use only the provided context"). This split means every agent inherits baseline behavior for free — see Agents.
2 · Fixed-role prompts (strict JSON contracts)
The analyzer, evaluator, and reviewer are judge/extractor roles with hardcoded prompts demanding exact JSON shapes:
Return ONLY valid JSON with this exact structure:
{ ... explicit schema with field semantics ... }
Rules:
- Return only JSON. - Do not include markdown. - <domain constraints>The backend enforces the contract with json.loads + defensive normalization; a violation raises and becomes a failed, observable ToolCall (Design Decisions).
Shared conventions
| Convention | Seen in |
|---|---|
| Role opening — "You are …" | every prompt |
Explicit Rules: block, one constraint per line | every prompt |
JSON schema shown literally, with enum values spelled out (low | medium | high) | analyzer, evaluator, reviewer |
| Mandated refusal/fallback strings | RAG answer ("I could not find this information…") |
| Temperature paired to role — judges 0, extractors 0.2, prose 0.3 | all (ladder) |
The catalog
Every prompt, verbatim from source, with design commentary:
| Prompt | Role | Output |
|---|---|---|
| Agent chat | wrap the agent's identity around a conversation | free text |
| Email analyzer | extract structure from raw email | strict JSON |
| Reply agent | draft the outbound reply | free text |
| Reviewer agent | judge the workflow package | strict JSON |
| RAG answer | answer only from context | grounded text |
| Retrieval evaluator | judge retrieval sufficiency | strict JSON |
Editing prompts safely
- Keep the JSON schemas in sync with parsers. The analyzer's fields are consumed by name in two routers; the reviewer's by the workflow finalizer. Renaming a field in the prompt without updating consumers breaks silently (
.get()defaults mask it). - Never weaken the refusal contract. The RAG refusal string is matched semantics across
refusal_nodeand the answer prompt — change both together or neither. - Mind the temperature pairing — don't raise a judge's temperature for "more interesting" verdicts.
- Test with recorded traces — replay real
WorkflowStepinputs from your dev DB against the new prompt before committing (Testing).
Related pages
- Prompt Catalog — the texts themselves
- AI Overview — where each prompt executes
- Evaluation — the two judge prompts in action