All products

Social Marketing AI Agent

In active development

An AI-native operating system for brand-led social marketing — it learns a brand, proposes strategy, produces channel-ready media, routes consequential actions for human approval, publishes safely, and improves from measured outcomes.

Role
Architect & sole engineer
Live at
smai.angabani.com
  • FastAPI
  • Python
  • React Native
  • Expo
  • TypeScript
  • PostgreSQL
  • pgvector
  • Supabase
  • Gemini
  • Cloudflare Workers AI
  • FLUX.1
  • n8n
  • MCP
  • OpenTelemetry
  • GitHub Actions
  • Render
  • Cloudflare Pages

Architecture, in one picture

Client — Expo / React Native Web · iOS · Android · one TypeScript codebase typed REST client Backend — FastAPI modular monolith one deployable, explicit module bounds identity brand brain strategy content analytics platform ops publishing approvals is_current_approval() approved · scoped · not expired evaluated live, fails closed PostgreSQL system of record + append-only audit pgvector brand knowledge chunks + embeddings Object storage generated media served as signed URLs Provider ports llm · embeddings · image social · storage swappable Gemini Workers AI FLUX.1 social · mock authorisation path — no provider call happens before this passes Dimmed = contract-faithful mock, not yet a real provider.
Every outward action funnels through one gate. is_current_approval() is computed at call time, not cached from generation time, so revoking an approval or letting one expire stops a publish that was already queued.
Brand context Strategy human approves Weekly plan human approves Copy + image generated, cited Approval inbox risk flags · expiry changes requested — terminal approved Re-run output is immutable Publish attempt gate re-checked here new lineage Metrics ingested per post Insight recommendation feeds the next cycle human gate terminal state human re-runs generation manually every transition writes an AuditEvent
changes_requested is terminal on purpose. Generated content is append-only and versioned, so a reviewer never edits model output in place — they reject it and re-run generation, which produces a new lineage and keeps the audit trail honest about what the system actually produced.

1. System Overview

The product is built as a modular monolith rather than a microservices sprawl or a thin “chat wrapper” around an LLM. Every AI capability (text generation, embeddings, image generation, publishing, analytics) sits behind a replaceable provider port/adapter, so the underlying model or platform can be swapped without touching business logic.

┌─────────────────────────────────────────────────────────────────┐
│                        CLIENT (Expo)                             │
│   React Native + TypeScript · Web, iOS, Android · Expo Router    │
└───────────────────────────────┬───────────────────────────────────┘
                                 │ typed API client (REST)
┌───────────────────────────────▼───────────────────────────────────┐
│                    BACKEND (FastAPI · Python)                    │
│         Modular monolith — one deployable, clear module            │
│  bounds: identity · brand brain · strategy · content · approvals │
│           · publishing · analytics · platform ops                │
└───┬──────────────┬──────────────┬──────────────┬─────────────────┘
    │              │              │              │
┌───▼───┐   ┌───────▼──────┐  ┌────▼────┐  ┌──────▼───────┐
│Postgres│   │  pgvector    │  │ Object  │  │  Provider    │
│(system │   │ (embeddings/ │  │ Storage │  │  Registry    │
│of      │   │    RAG)      │  │ (media) │  │ (LLM/image/  │
│record) │   │              │  │         │  │ social/etc.) │
└────────┘   └──────────────┘  └─────────┘  └──────────────┘

Non-negotiables baked into every layer:

  • Human approval is mandatory by default for any consequential public action (publish, reschedule, delete). The system fails closed without a valid, scoped, unexpired approval.
  • No fabricated success — a provider action is never reported as “done” until it’s confirmed and reconciled.
  • Every external write, publish, or spend action requires an explicit, auditable authorization step.

2. Tech Stack

Layer Technology Role
Client Expo + React Native + TypeScript, Expo Router Single codebase → Web, iOS, Android
Backend FastAPI (Python), modular monolith Identity, Brand Brain, Strategy, Content, Approvals, Publishing, Analytics
Database PostgreSQL (Supabase) Transactional source of truth
Vector store pgvector (in Postgres) Embeddings for brand-knowledge RAG
Object storage Supabase Storage, behind an ObjectStorage port Generated images, brand assets, signed URLs
Auth Supabase Auth Email + one-time-code sign-in
LLM (text) Gemini 3.6 Flash (primary), Cloudflare Workers AI Llama 3.1 8B (secondary adapter) Strategy, copy, brand-profile extraction — all behind an llm.generate port
Embeddings gemini-embedding-001 Brand-source chunking + retrieval
Image generation Cloudflare Workers AI — FLUX.1-schnell Branded social image generation, behind an image.generate port
Automation / orchestration n8n (self-hosted via Docker) Workflow orchestration layer for publish/schedule jobs — architected, integration in progress (see §5)
Tool exposure MCP (Model Context Protocol) Scoped, auditable tool access for automation — cannot bypass app-level authorization
CI/CD GitHub Actions Lint, type-check, tests, security scans, AI eval gates, migrations, build, staged release, smoke test, rollback
Hosting Render (backend), Cloudflare Pages (client + landing) Both live in production
Observability OpenTelemetry-standard instrumentation Tracing/metrics across services

Design principle behind the stack: standards-first, free-first, replaceable-everywhere. Every provider (LLM, embeddings, image gen, social platform, storage) is chosen for a fast, low/no-cost start, with a clear swap path to a stronger provider later — never a permanent architectural bet on one vendor.


3. Domain Model (Core Entities)

Workspace ── Member ── Role ── Policy
Brand ── BrandProfile ── BrandSource ── KnowledgeChunk
Objective ── Audience ── Campaign ── Strategy ── Hypothesis
ContentPlan ── ContentItem ── PlatformVariant ── Asset
Review ── Comment ── Approval ── PolicyDecision
Schedule ── PublishJob ── ProviderPost
MetricSnapshot ── Insight ── Recommendation ── Experiment
ProviderConfig ── Capability ── CredentialRef
PromptTemplate ── AgentRun ── EvalRun ── AuditEvent

Every generated artifact (a strategy, a piece of copy, an image) is versioned and audit-logged — nothing is silently overwritten, and every AI-generated claim carries a citation back to its source brand knowledge.


4. The Core Product Loop (State Machine)

This is the canonical lifecycle every piece of content moves through, end to end:

BrandContextApproved


StrategyProposed ──► StrategyApproved


PlanProposed ──► PlanApproved


ContentDrafted ──► MediaReady


ReviewRequested ──► ChangesRequested / ContentApproved


PublishScheduled ──► PublishAttempted ──► Published / PublishFailed


MetricsIngested ──► InsightGenerated ──► RecommendationProposed

        └──────────────► (feeds back into next Strategy cycle)

Every transition is logged to an AuditEvent table with actor, timestamp, and payload — this is what makes “0 unapproved public actions” a verifiable claim rather than a policy statement.


5. AI Flows — In Detail

5.1 Brand Brain (Ingestion + RAG)

Status: Implemented

Brand sources (docs, URLs, past posts)


  Chunking + embedding (gemini-embedding-001)


   pgvector store (KnowledgeChunk table)


extract_brand_profile() ── reads the brand's full indexed corpus


BrandProfile (versioned) ── every claim validated against its
                             source citation before being trusted


get_confirmed_profile() ── the single grounding source every
                            downstream generation step reads from

Every fact the AI later states about a brand (tone, prohibited claims, positioning) traces back to a real citation in the ingested corpus — this is what keeps groundedness failures near zero rather than hallucinated.

5.2 Strategy & Weekly Plan Generation

Status: Implemented

  • Reads the confirmed BrandProfile + workspace Objective/Audience/Campaign data.
  • Produces a versioned Strategy and a WeeklyPlan (child WeeklyPlanItem rows), each requiring explicit human approval before content generation begins.
  • Every approve/revoke action is captured by a shared ApprovalAuditEvent log.

5.3 Copy Generation + Platform Adaptation

Status: Implemented

Approved WeeklyPlan + Strategy + confirmed BrandProfile


generate_copy_for_item()

        ├─► per-fact/offer citation (grounded, not free-form)
        ├─► platform constraints enforced TWICE:
        │       1. in the generation prompt
        │       2. deterministically, post-generation


GeneratedCopyItem (one row per platform variant,
                   grouped by lineage_id)

5.4 Image Generation

Status: Implemented

  • image.generate port → Cloudflare FLUX.1-schnell adapter.
  • generate_branded_image() produces on-brand visual assets tied to a ContentItem.
  • Stored via the ObjectStorage port; served to the client as signed URLs, never raw storage keys.

5.5 Human-in-the-Loop Approval + Risk Flagging

Status: Implemented

This is the safety-critical AI flow — every generated asset (image or copy) is intercepted before it can move toward publishing:

Generated asset (pending_approval)


Approval Inbox (list + single-item view)

        ├─► Risk-flag detection: generated text scanned live against
        │       the brand's CURRENT prohibited-claims list
        │       (not frozen at generation time — a claim added
        │        after generation still surfaces on old pending items)

        ├─► Citations, context version, and expiry surfaced per item


   Human decision:
   Approve │ Reject (reason) │ Request Changes │ Revoke │ Comment


is_current_approval() ── computed live on every check:
   status == "approved" AND (no expiry OR not yet expired)
   → the fail-closed gate every publish action must pass

changes_requested is intentionally a terminal state, not a return-to-draft loop — generated content is immutable once created (append-only, versioned), so a human re-runs generation rather than editing AI output in place.

5.6 Publishing (mock today, real-provider path in progress)

Status: Mock provider implemented; real social adapters next

  • All publish/schedule/analytics flows currently run against contract-faithful mock providers — this was a deliberate MVP decision so the entire approval → publish → metrics loop could be built, tested, and demoed safely before any real OAuth/social credentials were needed.
  • Every publish action re-checks is_current_approval() immediately before execution — this is the concrete enforcement point for the “no unapproved public action” principle.
  • Threads is the first real social connector being integrated (free, lighter OAuth than Instagram/Facebook, faster App Review). Instagram/Facebook follow once Meta Business Verification clears (external, multi-week process, running in parallel).

5.7 Analytics & Recommendations

Status: Next in the build sequence

  • MetricSnapshot ingestion → Insight generation → Recommendation proposals, closing the loop back into the next Strategy cycle.
  • This is the step that turns the system from “generate and publish” into “learn and improve.”

6. n8n & Orchestration Layer — Detailed

Current status: architected, not yet wired in. Being transparent about this because it matters for an accurate showcase: today, publish jobs are triggered directly by the FastAPI backend, not by n8n. The design below is the intended integration, next on the roadmap.

Why n8n

The system is explicitly designed so automation/orchestration tooling is never the system of record and can never bypass app-level authorization. n8n was chosen because:

  • It’s self-hostable (Docker), keeping workflow logic out of a vendor’s cloud.
  • It’s visual/inspectable — a non-engineer can see what a publish workflow actually does, which matters for an auditable marketing system.
  • It’s replaceable — like every other provider in this stack, orchestration sits behind a boundary rather than being load-bearing infrastructure baked into the core.

Intended architecture

FastAPI (system of record)

        │  publish job created + is_current_approval() already verified
        │  (n8n is only ever invoked AFTER this gate passes —
        │   it never makes the approval decision itself)

   n8n workflow (Docker, self-hosted)

        ├─► Step 1: fetch PublishJob + ProviderPost details via
        │            scoped MCP tool call (read-only, least privilege)
        ├─► Step 2: call the appropriate social.publish adapter
        │            (mock provider today; Threads/Meta adapters next)
        ├─► Step 3: handle provider response
        │            success → reconcile ProviderPost, mark Published
        │            failure → mark PublishFailed, surface retry
        ├─► Step 4: emit AuditEvent for the attempt (success or failure)

FastAPI receives the reconciled result via webhook


Approval Inbox card updates in place (Published / Publish Failed + retry)

Why this boundary matters

  • n8n never decides whether something is allowed to publish — that decision (is_current_approval()) is computed and enforced entirely inside the FastAPI backend, before n8n is ever invoked. This satisfies the non-negotiable principle that automation cannot bypass app authorization.
  • n8n’s role is purely execution and retry orchestration for already-approved work — handling provider-specific quirks (rate limits, retries, webhook callbacks) without that logic living inside the core request/response cycle of the API.
  • Because it’s swapped in behind a workflow.run-style boundary, the same publish logic could run without n8n at all (direct backend calls, as it does today) — n8n is an operational convenience layer, not a dependency the core product logic requires.

7. Security, Governance & Reliability Principles

  • Least privilege & tenant isolation across every workspace/brand boundary.
  • Prompt-injection defense treated as a first-class concern, not an afterthought, anywhere user- or web-sourced content reaches an LLM call.
  • Fail-closed approvals — the system defaults to not acting without a valid, current, scoped approval; it never defaults to “probably fine.”
  • No fabricated success — provider actions are only ever reported as complete once confirmed and reconciled against the provider’s actual response.
  • Full audit trail — every state transition, approval decision, and publish attempt is logged with actor and timestamp.
  • CI/CD as a real gate, not a formality — lint, type-check, full test suite, and AI eval gates all run automatically before anything merges, including a dedicated hardening pass to keep the commit gate itself from silently failing open.

8. Current Build Status Snapshot

Capability Status
Brand ingestion + RAG (pgvector) ✅ Built, tested
Brand Profile extraction + citations ✅ Built, tested
Strategy + weekly plan generation ✅ Built, tested
Copy generation + platform adaptation ✅ Built, tested
Branded image generation ✅ Built, tested, live-demoed
Approval Inbox (risk flags, citations, expiry, comments) ✅ Built, tested, live-demoed
Mock publish + scheduling 🔜 Next in sequence
n8n orchestration layer 🔜 Architected, integration pending
Real social connector (Threads first) 🔜 In progress
Mock analytics + recommendations 🔜 Planned
Subscription / account tiering 🔜 In design

This document reflects the architecture as actually implemented and committed to the codebase, distinguishing built capabilities from designed-but-not-yet-integrated ones (notably n8n) so it can be shown externally without overstating current state.

Next

See the rest of the work, or get in touch.