Companion to: Engineering Summary · Updated: 2026-10-28 · Describes: master at 5d1094f (2026-10-26)
This addendum describes how MediaRaq is structured, how a persona agent handles a conversation, how content moves from plan to publication, how the platform is deployed, and why the key decisions were made. Diagrams are Mermaid.
Contents
- System context
- Logical components
- Persona agent architecture
- Memory and retrieval
- Agent actions and human approval
- Operator Copilot and the MCP tool surface
- Model routing
- Content production pipeline
- Events and reliability patterns
- Identity, tenancy and security
- Deployment topology
- Observability
- Delivery and release gates
- Architecture decision log
1. System context
flowchart LR
operator([Business operator])
visitor([Visitor / customer])
aiclient([Claude Desktop · ChatGPT])
subgraph MR["MediaRaq"]
studio[Studio web app]
agents[Persona agents · Copilot]
production[Media production]
end
llm[(LLM providers<br/>Anthropic · OpenAI · Azure OpenAI · Gemini)]
media[(Media providers<br/>fal.ai · HeyGen · ElevenLabs · …)]
social[(Social platforms<br/>Instagram · YouTube · TikTok · X)]
web[(WordPress)]
crm[(CRMs<br/>EspoCRM · HighLevel)]
msg[(Channels<br/>public chat · WhatsApp · email)]
operator --> studio
aiclient -- MCP --> agents
visitor <--> msg <--> agents
studio --> agents
studio --> production
agents --> llm
production --> media
production --> social
production --> web
agents -- approved actions --> crm
| Actor |
What they do |
| Operator |
Creates and tunes personas, plans and approves content, approves agent actions, reviews analytics |
| Visitor |
Talks to a persona on its public page or a messaging channel; may become a lead or book an appointment |
| AI client |
Uses MediaRaq's MCP server to read data and create or configure personas from chat |
2. Logical components
flowchart TB
cf[Cloudflare] --> ngx[nginx · per-route configs]
studio[Studio · React/Vite on Cloudflare] --> facade
ngx --> par & oaw & facade
subgraph agentic[Agent tier]
par[persona-agent-runtime]
conv[conversations-service]
oaw[operator-agent-worker · Copilot]
mcp[mcp-server · 69 tools]
end
subgraph prod[Production tier]
facade[media-api-facade]
workers[script · image · video · qc · publishing<br/>persona-evolution · affiliate · voice-intake]
end
subgraph data[Data tier]
pg[(PostgreSQL + pgvector)]
kafka[[Kafka · 25 topics]]
redis[(Redis)]
minio[(MinIO)]
end
conv --> par
oaw --> mcp --> facade
par --> pg & redis
facade --> pg & kafka & minio
kafka <--> workers
workers --> pg & minio
| Component |
Responsibility |
persona-agent-runtime |
Runs persona agents: decision tools, memory retrieval, generation, action proposals, embedding worker |
conversations-service |
Channels and messaging providers, persona agent factory, memory backfills |
operator-agent-worker |
The operator Copilot: planning, MCP tool use, confirm-before-write, checkpoints |
mcp-server |
First-party MCP server: tenant-scoped tools for personas, content, analytics, offers, productions |
media-api-facade |
Orchestration boundary for media jobs, assets, persona creation, Reels storyboards, billing, SSE snapshots |
image-service |
Image Platform: canonical image request contract, Image Prompt Engineer agent, provider routing |
video-service, script-service, qc-service |
Video generation, script variants with persona continuity, quality approval |
publishing-orchestrator |
Scheduled and on-demand publishing per destination, with retries and readiness checks |
persona-evolution-service |
Persona believability and change over time |
affiliate-service, voice-intake |
Affiliate offers; signed audio intake for voice cloning |
3. Persona agent architecture
Personas are data, compiled into agents at runtime:
flowchart LR
persona[(Persona record<br/>canon · profile · visual identity)] --> kernel[Compressed persona kernel]
policy[(Launch profile<br/>tool grants · model policy · safety)] --> factory
kernel --> factory[Persona agent factory]
factory --> def[Agent definition<br/>instructions · ModelRoutingPolicy · SafetyPolicy · ToolGrants · MemoryScope]
def --> runtime[persona-agent-runtime]
One conversation turn:
sequenceDiagram
participant V as Visitor
participant R as Agent runtime
participant T as Decision tools
participant M as Memory (hybrid RAG)
participant L as LLM (routed)
V->>R: message
R->>R: preflight (message size, token budget)
R->>T: run granted decision tools
T->>M: persona.memory.retrieve / persona.context.lookup
M-->>T: ranked items with source, revision, consent
T-->>R: tool results (incl. lead or appointment proposal)
R->>R: revalidate sources · build prompt within context budget
R->>L: generate (prompt cache, per-turn cost ceiling)
L-->>R: response
R-->>V: reply (streamed)
R->>R: record proposals, write memories, queue embeddings
- No code per persona. Tool grants, model choice and safety rules are configuration on the persona's launch profile.
- Decision tools run before generation, so the response is grounded in what the tools found, and the tool results are kept with the turn.
- Cost is bounded per turn by a hard ceiling, and system prompts use prompt caching.
4. Memory and retrieval
| Memory type |
Holds |
Retrieved by |
| Working |
The current conversation, a rolling summary and the history policy |
Context builder |
| Episodic |
What a specific person said before, per channel (public page, WhatsApp and others) |
Hybrid search scoped to that person, with consent checks |
| Semantic |
Business knowledge chunks and persona memories |
Hybrid lexical + pgvector search scoped to tenant and persona |
| Procedural |
How this persona handles situations |
Procedure lookup, revalidated before use |
- Hybrid retrieval. Lexical and vector candidates are merged and ranked. The query is embedded once per turn and reused by every lookup in that turn. If vector search fails, retrieval falls back to lexical and logs it.
- Grounding checks. Every item carries its source ID and revision. Before an item reaches the prompt, the runtime confirms that the source is still current and the person's consent still holds.
- Continuous embedding. A drain worker embeds new memories with a fresh one-tenant grant per job and tracks spend per persona.
- Evaluation and gates. A frozen, signed, budget-capped evaluation scores each retrieval mode against release gates (see the Engineering Summary).
5. Agent actions and human approval
flowchart TB
agent[Persona agent] -->|"lead.capture.propose · appointment.propose · ghl.contact.create"| prop[(Action proposal<br/>idempotency key · required-field check)]
prop --> review{Operator approves?}
review -- yes --> exec[Provider adapter]
review -- no --> rejected[Rejected and recorded]
exec --> espo[EspoCRM Lead<br/>signed API · correlation ID]
exec --> ghl[HighLevel via MCP<br/>Microsoft Agent Framework client]
espo & ghl --> result[(Sanitized execution result<br/>stored on the proposal)]
- Proposals are provider-neutral. Only the approved execution step is provider-specific, so adding a CRM means adding an adapter, not changing the agent.
- Idempotency. Repeating an approval can't create duplicate records: the adapter looks the record up by correlation ID first.
- Routing is data. A persona can promote several clients, and HighLevel sites are first-class inventory assigned to personas (ADR 0008, ADR 0009).
- MediaRaq's own MCP server exposes 69 tenant-scoped tools to the Copilot and to outside AI clients (Claude Desktop, ChatGPT). The tools cover personas, content, analytics, affiliate offers, article lanes and agent runs, the image workbench, and the import of externally produced storyboards.
- The Copilot discovers tools at runtime and starts each session from an operator handbook served by a tool. It classifies tools by metadata, tags write-intent tools, and shows a diff and asks for confirmation before any write.
- Identity is propagated end to end: a service credential for the worker, plus the operator's own token and tenant, operator and session IDs for scoping and audit.
7. Model routing
| Layer |
Options |
Default and mechanism |
| Persona agents |
Anthropic, OpenAI, Azure OpenAI, Gemini |
Claude Haiku 4.5; provider registry plus per-persona ModelRoutingPolicy |
| Image Prompt Engineer |
Gemini, Claude, OpenAI |
Gemini; selected at runtime |
| Copilot planner |
Anthropic, OpenAI |
Composite planner |
| Image generation |
fal.ai model family (FLUX, Seedream, Nano Banana, Kontext and others) |
Image Platform "effective model" from DB-backed presets; per-panel overrides |
| Video and voice |
fal.ai video models, HeyGen, Kling, Seedance, ElevenLabs |
Presets and quality lanes; visible fallback |
The Image Prompt Engineer is itself a tool-using agent. It looks up model-family rules and endpoint constraints, retrieves cited prompting guidance from a curated knowledge pack, and composes provider-specific prompts. It was adopted only after running in shadow (see the Engineering Summary).
8. Content production pipeline
flowchart LR
plan[content.plan.created] --> script[script-service]
script --> sgc[script.generation.completed]
sgc --> img[image-service] --> igen[image.asset.generated]
sgc --> vid[video-service] --> vgen[video.asset.generated]
igen & vgen --> qc[qc-service] --> appr[qc.*.approved]
appr --> pub[publishing-orchestrator] --> dest[Instagram · YouTube · TikTok · X · WordPress]
- Reels are storyboard-driven. A storyboard specification is decomposed into scenes. Keyframes are generated with persona-consistent references. Per-scene video and narration are produced asynchronously, with live progress over SSE, then composed.
- Publishing treats each destination as its own publication with its own state, attempts and results (ADR-0021). PostgreSQL is the source of truth, and Kafka carries the work.
9. Events and reliability patterns
| Pattern |
Where |
Transactional outbox with a durable relay (PostgreSQL LISTEN/NOTIFY as a hint, FOR UPDATE SKIP LOCKED claims, acknowledged Kafka sends) |
Checkout requests, image terminal events, other cross-service events |
| Dead-letter topics and classified poison messages before offset commit |
Intake and checkout consumers |
| Idempotency keys |
Payment sessions, CRM writes, publication requests |
| Terminal-event durability |
Image results are either acknowledged by Kafka or written to an outbox before the source message is committed |
| No polling |
Operator-facing progress uses server-sent events; a guard script blocks reintroducing polling |
10. Identity, tenancy and security
| Layer |
Control |
| Identity |
Clerk for operators; workspaces map to internal tenants |
| Authorization |
Tenant memberships with roles (owner, admin, creator, editor, analyst, viewer), enforced in every API and worker from validated tokens |
| Isolation |
Retrieval, embedding and publishing are bound to tenant and persona; a second-tenant isolation proof was run before multi-tenant launch |
| Service identity |
Service-to-service credentials; scoped MCP service principal for the Copilot |
| Secrets |
Environment files on the server, plus an encrypted vault for per-tenant integration credentials; agents only ever see secret names |
| Webhooks |
Provider signatures verified; each webhook on its own nginx route |
| Agent safety |
Per-persona safety policy, message-size and token-budget preflight, approval-gated external actions, cost ceilings |
| Data |
Visitor memories require consent; retention limits on agent sessions |
11. Deployment topology
flowchart TB
users([Users]) --> cf[Cloudflare]
cf --> pages[Studio<br/>Cloudflare]
cf --> vm
subgraph vm[Cloud VM · Docker Compose]
ngx[nginx] --> svc[Backend services and agents]
svc --> pgb[PgBouncer] --> pg[(PostgreSQL + pgvector<br/>pinned image digest)]
svc --> k[[Kafka]]
svc --> r[(Redis)]
svc --> m[(MinIO)]
obs[OTel Collector · Loki · Tempo · Grafana · GlitchTip]
end
svc -.-> obs
| Choice |
Why |
Next step when it saturates |
| One VM running Docker Compose |
Lowest cost and operational overhead at current load; every service is still containerized |
Managed PostgreSQL and Kafka, then a container platform |
| PgBouncer in front of PostgreSQL |
Many services, bounded connections |
Read replica for analytics |
| Studio on Cloudflare |
Global static delivery, separate from backend deploys |
— |
| Self-hosted observability |
Full traces and logs without per-GB SaaS cost |
Managed backend if retention needs grow |
12. Observability
- OpenTelemetry traces and logs into Tempo and Loki, with Grafana as the engineering console and Prometheus metrics.
- GlitchTip (Sentry-compatible) for exceptions, following a written exception and logging standard: structured errors, correlation IDs, sanitized messages.
- Agent turns record their tool results, retrieval mode, cost and fallbacks. Generated images record which prompt was used and why.
- Post-deploy verification includes a synthetic log query through the observability stack.
13. Delivery and release gates
flowchart LR
h[Dated handoff] --> chk{Handoff checker}
chk --> wt[Agent works in its own git worktree]
wt --> val[Dated validation record<br/>incl. what was NOT verified]
val --> rev{Architect review<br/>against code and live system}
rev --> merge[Merge to master<br/>archive tag for the branch]
merge --> base[Critical baseline]
base --> deploy[Guarded VM deploy]
deploy --> cap[Capability checks<br/>before and after activation]
cap --> obs[Observability verification]
- The deploy script refuses a dirty checkout or a non-fast-forward update. A database-image release gate blocks unreviewed images, and a verified backup is taken before risky releases.
- The Studio deploys separately to Cloudflare.
14. Architecture decision log
| # |
Decision |
Alternatives considered |
Rationale |
| 1 |
Personas compiled into agents from data |
Code per persona; one shared prompt |
New agents without code; consistent safety, memory and tools |
| 2 |
Hybrid lexical + pgvector retrieval in PostgreSQL |
Separate vector database; vector-only search |
One system of record; tenant filters in SQL; measurably better recall |
| 3 |
Approval-first, provider-neutral agent actions |
Direct CRM writes from the agent |
No unreviewed writes to customer systems; adapters per CRM |
| 4 |
First-party MCP server as the tool surface |
Bespoke Copilot API |
One tool surface for the Copilot and external AI clients |
| 5 |
Shadow before adoption for new agents |
Feature-flag switch-over |
Evidence on real traffic before it affects output |
| 6 |
Provider portfolio behind a media facade |
Single vendor |
Routing by quality, cost and availability; visible fallback |
| 7 |
Kafka plus transactional outbox |
Synchronous calls; fire-and-forget |
No lost events; retries and replay |
| 8 |
Server-sent events for operator feedback |
Polling |
Live feedback, lower load |
| 9 |
Clerk identity, internal tenants as authority |
Clerk organizations as the only authority |
Backend owns authorization and isolation |
| 10 |
Media analysis split into separate services (extractor, transcription, vision, synthesis) |
One analysis service |
Independent scaling and failure (ADRs 0001–0005) |
| 11 |
Storyboard specification with parallel scene decomposition |
Linear generation |
Parallel scene work; per-scene regeneration (ADRs 0011–0021) |
| 12 |
Per-destination publication records |
One post, many platforms |
Independent outcomes and retries per platform (ADR-0021) |
| 13 |
Single VM with Compose at current stage |
Kubernetes now |
Cost and simplicity; containerized for a later move |