Facebook Publishing Pipeline v2 — Documentazione Completa

Versione: 2.0 Data: 2026-03-02 Autore: Claude Code / Antigravity Path base: /root/projects/agents/


1. PANORAMICA

La Facebook Publishing Pipeline è un sistema multi-agente che trasforma un topic testuale in un post pubblicato sulla pagina Facebook di Galanti.digital. Il sistema è composto da cinque agenti Python specializzati orchestrati da n8n v2.

Input: un topic + parametri opzionali (tono, stile immagine) Output: post pubblicato su Facebook (testo + immagine) + notifica Telegram + log su Postgres


2. ARCHITETTURA

UTENTE / CLAUDE CODE
        │
        ▼ POST webhook
┌──────────────────┐
│  n8n Webhook     │  https://n8n-v2.m0t0.cloud/webhook/facebook-pipeline-start
│  Trigger         │
└────────┬─────────┘
         │
         ▼
┌──────────────────┐
│  ResearchAgent   │  :8603 — cerca info su Perplexity AI
│  /research       │  → summary, sources, key_facts
└────────┬─────────┘
         │ (fork parallelo)
    ┌────┴────┐
    ▼         ▼
┌──────────┐ ┌──────────────┐
│ Content  │ │ ImageAgent   │
│ Agent    │ │ :8605        │
│ :8604    │ │ /generate    │
│ /write   │ │ → image_url  │
│ → text   │ └──────┬───────┘
│ hashtags │        │
│ cta      │        │
└────┬─────┘        │
     └─────────┬────┘
               ▼
     ┌──────────────────┐
     │  ReviewAgent     │  :8606 — verifica conformità persona
     │  /review         │  → approved, revised_text, violations
     └────────┬─────────┘
              │
         ┌────┴────┐
         ▼         ▼
    [approved]  [rejected]
         │         │
         ▼         ▼
┌─────────────┐  ┌──────────────────┐
│ Facebook    │  │ Rejected Response │
│ Publisher   │  │ (webhook 422)    │
│ :8602       │  └──────────────────┘
│ /publish    │
└──────┬──────┘
       │ (fork parallelo)
  ┌────┴────┐
  ▼         ▼
Telegram  Postgres
Notify    Log
  │         │
  └────┬────┘
       ▼
  Success Response (webhook 200)

3. AGENTI — DETTAGLIO TECNICO

3.1 Stack comune

Tutti gli agenti condividono: - Framework: Agno 2.5.5 - Runtime: Python 3.12.3 in venv (/root/projects/agents/venv/) - Modello primario: claude-sonnet-4-6 via LiteLLM proxy :4000 - Modello fast: claude-haiku-4-5 via LiteLLM proxy :4000 - Config: /root/.env.agents - Persona: /root/projects/agents/shared/persona.md

3.2 ResearchAgent — :8603

File: specialists/research_agent.py Modello: get_fast_model() → claude-haiku-4-5 Tool: PerplexityTool (API sonar, real-time web)

Endpoint:

POST /research
{
  "topic": "string",        # argomento da ricercare
  "context": "string",      # contesto aggiuntivo (opzionale)
  "user_id": "giacomo"
}

Output:

{
  "status": "ok",
  "topic": "...",
  "summary": "riassunto in 3-5 frasi in italiano",
  "sources": ["url1", "url2"],
  "key_facts": ["fatto 1", "fatto 2", "fatto 3"]
}

Logica interna: 1. Costruisce query: topic + contesto opzionale 2. Chiama Perplexity API (sonar) con sistema in italiano 3. Parsa il JSON dalla risposta dell'agente 4. Fallback: se il JSON non è parsabile, restituisce il testo raw come summary

Variabile d'ambiente richiesta: PERPLEXITY_API_KEY


3.3 ContentAgent — :8604

File: specialists/content_agent.py Modello: get_model() → claude-sonnet-4-6 Tool: FileTools() (accesso a file locali)

Endpoint:

POST /write
{
  "topic": "string",
  "research_brief": "string",   # output summary da ResearchAgent
  "tone": "professionale",       # default
  "persona_file": "/root/projects/agents/shared/persona.md",
  "user_id": "giacomo"
}

Output:

{
  "status": "ok",
  "topic": "...",
  "text": "corpo del post senza hashtag e senza emoji",
  "hashtags": ["#hash1", "#hash2"],
  "cta": "frase call-to-action"
}

Logica interna: 1. Carica persona.md e la inietta nelle istruzioni sistema dell'agente 2. Costruisce prompt con topic + tone + research_brief 3. Richiede JSON con campi text, hashtags, cta 4. Regole obbligatorie: max 3 paragrafi, frasi attive, no bullet point, no emoji nel testo


3.4 ImageAgent — :8605

File: specialists/image_agent.py Modello: nessuno (direct tool call, niente LLM) Tool: FalTool (fal-ai/recraft-v3)

Endpoint:

POST /generate
{
  "topic": "string",
  "content_hint": "string",          # hint visivo specifico (opzionale)
  "style": "digital_illustration",   # default recraft-v3
  "image_size": "landscape_16_9",    # default 1200x628 Facebook
  "user_id": "giacomo"
}

Output:

{
  "status": "ok",
  "image_url": "https://...",
  "prompt_used": "professional design, clean, minimal, brand identity, ...",
  "style": "digital_illustration",
  "model": "fal-ai/recraft-v3"
}

Logica interna: 1. Prepende brand prefix fisso: "professional design, clean, minimal, brand identity, " 2. Se content_hint è valorizzato, lo usa come base; altrimenti usa topic 3. Chiama fal.ai direttamente via httpx (niente LLM coinvolto) 4. Timeout: 90 secondi

Variabile d'ambiente richiesta: FAL_KEY

Stili recraft-v3 disponibili: - digital_illustration (default) - realistic_image - vector_illustration - square_hd, portrait_4_3, altri preset dimensionali


3.5 ReviewAgent — :8606

File: specialists/review_agent.py Modello: get_fast_model() → claude-haiku-4-5 Tool: nessuno

Endpoint:

POST /review
{
  "text": "string",         # testo prodotto da ContentAgent
  "image_url": "string",    # URL immagine (opzionale)
  "hashtags": ["#h1"],      # hashtag da ContentAgent
  "persona_file": "/root/projects/agents/shared/persona.md",
  "user_id": "giacomo"
}

Output:

{
  "status": "ok",
  "approved": true,
  "revised_text": "testo corretto o originale",
  "revised_hashtags": ["#h1", "#h2"],
  "violations": [],
  "notes": "Nessuna modifica necessaria."
}

Checklist applicata: 1. Niente bullet point nel testo (-, *, •, numeri con punto) 2. Niente frasi passive ("viene usato", "è stato fatto") 3. Massimo 3 paragrafi 4. Nessuna emoji nel corpo del testo 5. Hashtag: massimo 5, tutti con # davanti 6. Tono: professionale ma accessibile 7. Call-to-action finale esplicita

Logica: se approved=false, il ReviewAgent corregge autonomamente il testo in revised_text.


3.6 FacebookPublisher — :8602 (v3 slim)

File: specialists/facebook_publisher.py Modello: nessuno (direct tool call) Tool: FacebookTool (Meta Graph API v18.0)

Endpoint publish:

POST /publish
{
  "text": "string",            # testo finale (body + hashtags + emoji)
  "image_url": "string",       # URL immagine (opzionale)
  "post_type": "photo|text",   # "photo" se image_url presente
  "link": "string",            # URL da allegare (opzionale)
  "user_id": "giacomo"
}

Endpoint dry-run:

POST /dry-run
{ stessi campi di /publish }

Output:

{
  "status": "ok",
  "dry_run": false,
  "post_type": "photo",
  "post_id": "123456789_987654321",
  "text_published": "...",
  "image_url": "https://...",
  "timestamp": "2026-03-02T..."
}

Logica post_type: - "photo" + image_urlPOST /{page_id}/photos (foto con caption) - "link" + linkPOST /{page_id}/feed (link attachment) - "text"POST /{page_id}/feed (testo puro)

Variabili d'ambiente richieste: - FACEBOOK_PAGE_ID (es. 2260289454212356) - FACEBOOK_PAGE_ACCESS_TOKEN (Page Token, si rinnova ogni ~60 giorni) - FACEBOOK_USER_TOKEN (fallback per derivare automaticamente il Page Token)


4. TOOLS — DETTAGLIO TECNICO

4.1 PerplexityTool

File: tools/perplexity_tool.py Classe: PerplexityTool(Toolkit) Modello API: sonar (Perplexity online, real-time)

Metodo esposto all'agente:

def search(self, query: str, focus: str = "internet") -> str

Restituisce JSON: {summary, sources, key_facts}

Configurazione API:

URL: https://api.perplexity.ai/chat/completions
Headers: Authorization: Bearer {PERPLEXITY_API_KEY}
max_tokens: 800, temperature: 0.2, return_citations: True

4.2 FalTool

File: tools/fal_tool.py Classe: FalTool(Toolkit) Modello default: fal-ai/recraft-v3

Metodo esposto:

def generate_image(self, prompt, image_size=None, style=None, width=None, height=None) -> str

Restituisce JSON: {image_url, prompt_used, model, style, image_size}

Configurazione API:

URL: https://fal.run/fal-ai/recraft-v3
Headers: Authorization: Key {FAL_KEY}
timeout: 90s

4.3 FacebookTool

File: tools/facebook_tool.py Classe: FacebookTool(Toolkit) API: Meta Graph API v18.0

Metodi registrati: | Metodo | Descrizione | |--------|-------------| | get_page_info() | Info pagina (nome, ID, follower) | | post_text(message) | Post testo puro | | post_with_link(message, link) | Post con URL allegato | | post_with_photo(photo_url, caption) | Foto con didascalia | | delete_post(post_id) | Cancella post | | list_recent_posts(limit=10) | Lista ultimi post |

Token resolution automatica: se FACEBOOK_PAGE_ACCESS_TOKEN non è impostato, il tool lo deriva da FACEBOOK_USER_TOKEN via Graph API.

Nota: i photo post NON sono editabili via Graph API. Solo i post testo sono modificabili.


5. SHARED MODULES

5.1 shared/config.py

Carica /root/.env.agents con python-dotenv. Espone:

Costante Default
LITELLM_URL http://localhost:4000
LITELLM_MASTER_KEY sk-cosmo-master-key-2026
AGENTS_DIR /root/projects/agents
MEMORY_DIR /root/projects/agents/memory
DEFAULT_MODEL claude-sonnet-4-6
FALLBACK_MODEL qwen2.5:7b

5.2 shared/llm.py

get_model(name=None)      # default: claude-sonnet-4-6
get_fast_model()          # claude-haiku-4-5
get_local_model()         # qwen2.5:7b (Ollama fallback)

Tutti usano LiteLLMOpenAI(base_url=LITELLM_URL, api_key=LITELLM_MASTER_KEY).

5.3 shared/persona.md

Definisce il tone of voice di Galanti.digital. Viene iniettata nelle istruzioni di ContentAgent e ReviewAgent.

Strutture post supportate: - Post breve (fino a 150 parole): aggancio → affermazione → CTA - Post medio (150–350 parole): aggancio → sviluppo 2-3 paragrafi → CTA - Post lungo/divulgativo (200–250 parole): incipit → sviluppo → CTA

Regole universali: - Niente frasi passive - Frasi max 25 parole - Max 5 hashtag (2 branded + 2 tematici + 1 trending) - Emoji: max 2 (breve/medio), max 3 (divulgativo)


6. WORKFLOW n8n

ID workflow: Q0M2xJVXi2AxFjxN Nome: Facebook Publishing Pipeline v2 Webhook: https://n8n-v2.m0t0.cloud/webhook/facebook-pipeline-start File: n8n_triggers/facebook-pipeline-v2.json

Payload di input (webhook POST):

{
  "topic": "Come l'AI sta cambiando il design",
  "tone": "professionale",
  "context": "focus su PMI italiane",
  "image": true,
  "image_style": "digital_illustration",
  "image_hint": "AI and design tools visual concept",
  "telegram_chat_id": "CHAT_ID"
}
Campo Tipo Default Note
topic string obbligatorio
tone string "professionale" Passato a ContentAgent
context string "" Contesto aggiuntivo per ricerca
image bool Non usato direttamente dal workflow (ImageAgent gira sempre)
image_style string "digital_illustration" Stile recraft-v3
image_hint string topic Prompt visivo alternativo per ImageAgent
telegram_chat_id string "" Chat ID per notifica (opzionale)

Flusso nodi n8n:

Nodo Tipo URL target Timeout
Webhook Trigger Webhook
Research Agent HTTP Request http://172.18.0.1:8603/research 60s
Content Agent HTTP Request http://172.18.0.1:8604/write 60s
Image Agent HTTP Request http://172.18.0.1:8605/generate 120s
Review Agent HTTP Request http://172.18.0.1:8606/review 60s
Approved? IF
Facebook Publish HTTP Request http://172.18.0.1:8602/publish 30s
Telegram Notify Telegram
Postgres Log Postgres facebook_posts_log
Success/Rejected Response Respond to Webhook

Nota sugli URL: n8n usa 172.18.0.1 (docker bridge gateway) per raggiungere i servizi sul host VPS. Il range localhost:86xx non è raggiungibile dall'interno del container.

Costruzione testo finale (nodo Facebook Publish):

{{ $('Review Agent').item.json.revised_text + '\n\n' + $('Content Agent').item.json.hashtags.join(' ') }}

Il testo finale unisce il corpo revisionato + gli hashtag separati da spazio.

Credenziali n8n:

Servizio Credential ID Nome
Telegram dvqA2GssfqHIAB2I Cosmo Operator
Postgres VexUUB0wgLGeeSBy Postgres account (V4)

Schema tabella Postgres:

-- Tabella: public.facebook_posts_log
CREATE TABLE IF NOT EXISTS facebook_posts_log (
    id SERIAL PRIMARY KEY,
    topic TEXT,
    post_id TEXT,
    text_published TEXT,
    image_url TEXT,
    approved BOOLEAN,
    review_notes TEXT,
    published_at TIMESTAMP
);

7. AVVIO E OPERATIVITÀ

Start pipeline completa:

/root/scripts/start_agents.sh

Questo avvia in tmux dedicato:

Sessione tmux Agente Porta
cosmo-orchestrator CosmoOrchestrator 8500
agent-builder AgentBuilder 8501
facebook-publisher FacebookPublisher v3 8602
agent-researcher ResearchAgent 8603
agent-content ContentAgent 8604
agent-image ImageAgent 8605
agent-reviewer ReviewAgent 8606

Verifica stato:

/root/scripts/start_agents.sh --status
# oppure manualmente:
curl http://localhost:8602/health
curl http://localhost:8603/health
curl http://localhost:8604/health
curl http://localhost:8605/health
curl http://localhost:8606/health

Stop:

/root/scripts/start_agents.sh --stop

Test singolo agente:

# Research
curl -s -X POST http://localhost:8603/research \
  -H "Content-Type: application/json" \
  -d '{"topic": "AI nel design grafico", "context": ""}' | jq .

# Content
curl -s -X POST http://localhost:8604/write \
  -H "Content-Type: application/json" \
  -d '{"topic": "AI nel design", "research_brief": "...", "tone": "professionale"}' | jq .

# Image
curl -s -X POST http://localhost:8605/generate \
  -H "Content-Type: application/json" \
  -d '{"topic": "AI e design", "style": "digital_illustration"}' | jq .

# Review
curl -s -X POST http://localhost:8606/review \
  -H "Content-Type: application/json" \
  -d '{"text": "testo del post", "hashtags": ["#AI", "#design"]}' | jq .

# Dry-run publisher
curl -s -X POST http://localhost:8602/dry-run \
  -H "Content-Type: application/json" \
  -d '{"text": "Testo test.", "post_type": "text"}' | jq .

Trigger diretto webhook (test end-to-end):

curl -s -X POST https://n8n-v2.m0t0.cloud/webhook/facebook-pipeline-start \
  -H "Content-Type: application/json" \
  -d '{
    "topic": "AI nel branding italiano",
    "tone": "professionale",
    "image": true,
    "image_style": "digital_illustration"
  }' | jq .

8. CONFIGURAZIONE AMBIENTE

File: /root/.env.agents

# LiteLLM
LITELLM_URL=http://localhost:4000
LITELLM_MASTER_KEY=sk-cosmo-master-key-2026

# AI APIs (via LiteLLM container — non modificare)
ANTHROPIC_API_KEY=...
OPENAI_API_KEY=...

# Perplexity
PERPLEXITY_API_KEY=pplx-...

# fal.ai
FAL_KEY=...

# Facebook
FACEBOOK_PAGE_ID=2260289454212356
FACEBOOK_PAGE_ACCESS_TOKEN=...   # si rinnova ogni ~60 giorni
FACEBOOK_USER_TOKEN=...          # fallback per derivare Page Token

# n8n
N8N_URL=https://n8n-v2.m0t0.cloud
N8N_API_KEY=...

# Modelli
DEFAULT_MODEL=claude-sonnet-4-6
FALLBACK_MODEL=qwen2.5:7b

9. DIPENDENZE PYTHON

Installate nel venv /root/projects/agents/venv/:

agno==2.5.5
anthropic
openai
fastapi
uvicorn
httpx
pydantic
python-dotenv
rich
litellm
sqlalchemy

Installazione da zero:

cd /root/projects/agents
python3 -m venv venv
source venv/bin/activate
pip install agno anthropic openai fastapi uvicorn httpx pydantic python-dotenv rich litellm sqlalchemy

10. NOTE TECNICHE E LIMITAZIONI NOTE

Agno 2.5.5 — breaking changes

API obsoleta Sostituzione
SqliteAgentStorage agno.db.sqlite.SqliteDb con db=
SqliteMemoryDb idem
RunResponse RunOutput
LiteLLM(api_base=) LiteLLMOpenAI(base_url=) per proxy locale

Bug noto: Agno invia temperature + top_p insieme ad Anthropic → errore 400. Fix in shared/llm.py: non passare top_p per modelli Claude.

Meta Graph API

n8n → host networking

I nodi n8n usano 172.18.0.1 (docker bridge gateway) per raggiungere i servizi sul host. La porta localhost:86xx non è raggiungibile dall'interno del container n8n. Il firewall UFW blocca attualmente le connessioni da 172.18.0.1 — se il workflow fallisce, verificare le regole UFW.

Perplexity


11. STRUTTURA FILE COMPLETA

/root/projects/agents/
├── venv/                          # Python 3.12.3 virtualenv
├── shared/
│   ├── config.py                  # Costanti, carica .env.agents
│   ├── llm.py                     # get_model() / get_fast_model()
│   ├── logging.py                 # Logger rich
│   ├── persona.md                 # Tone of voice Galanti.digital
│   └── registry.md                # Registro agenti attivi
├── specialists/
│   ├── research_agent.py          # :8603 — Perplexity web research
│   ├── content_agent.py           # :8604 — Facebook post writer
│   ├── image_agent.py             # :8605 — fal.ai image generation
│   ├── review_agent.py            # :8606 — Content QA / enforcement
│   └── facebook_publisher.py      # :8602 — Meta Graph API publisher (v3 slim)
├── tools/
│   ├── perplexity_tool.py         # Agno Toolkit — Perplexity API
│   ├── fal_tool.py                # Agno Toolkit — fal.ai recraft-v3
│   ├── facebook_tool.py           # Agno Toolkit — Meta Graph API v18.0
│   ├── tmux_tool.py               # Agno Toolkit — tmux sessions
│   └── n8n_api_tool.py            # Agno Toolkit — n8n v2 API
├── meta/
│   └── agent_builder.py           # AgentBuilder meta-agent :8501
├── orchestrators/
│   └── cosmo_orchestrator.py      # CosmoOrchestrator :8500
├── memory/                        # SQLite DB per sessioni agenti
│   ├── research_agent.db
│   ├── content_agent.db
│   └── review_agent.db
├── n8n_triggers/
│   └── facebook-pipeline-v2.json  # Workflow n8n importabile
└── specialists/
    └── ...

12. COME AGGIUNGERE UN NUOVO POST (WORKFLOW COMPLETO)

Via n8n (raccomandato):

curl -X POST https://n8n-v2.m0t0.cloud/webhook/facebook-pipeline-start \
  -H "Content-Type: application/json" \
  -d '{
    "topic": "ARGOMENTO DEL POST",
    "tone": "professionale",
    "image_style": "digital_illustration",
    "telegram_chat_id": "IL_TUO_CHAT_ID"
  }'

Via Claude Code (orchestrazione diretta):

# 1. Research
RESEARCH=$(curl -s -X POST http://localhost:8603/research \
  -H "Content-Type: application/json" \
  -d '{"topic": "ARGOMENTO"}')

SUMMARY=$(echo $RESEARCH | jq -r '.summary')

# 2. Content (parallelo a image)
CONTENT=$(curl -s -X POST http://localhost:8604/write \
  -H "Content-Type: application/json" \
  -d "{\"topic\": \"ARGOMENTO\", \"research_brief\": \"$SUMMARY\", \"tone\": \"professionale\"}")

# 3. Image (in parallelo)
IMAGE=$(curl -s -X POST http://localhost:8605/generate \
  -H "Content-Type: application/json" \
  -d '{"topic": "ARGOMENTO", "style": "digital_illustration"}')

# 4. Review
TEXT=$(echo $CONTENT | jq -r '.text')
HASHTAGS=$(echo $CONTENT | jq '.hashtags')
IMAGE_URL=$(echo $IMAGE | jq -r '.image_url')

REVIEW=$(curl -s -X POST http://localhost:8606/review \
  -H "Content-Type: application/json" \
  -d "{\"text\": \"$TEXT\", \"hashtags\": $HASHTAGS, \"image_url\": \"$IMAGE_URL\"}")

# 5. Pubblica se approvato
APPROVED=$(echo $REVIEW | jq -r '.approved')
if [ "$APPROVED" = "true" ]; then
  REVISED=$(echo $REVIEW | jq -r '.revised_text')
  FINAL_TEXT="$REVISED\n\n$(echo $HASHTAGS | jq -r 'join(" ")')"

  curl -s -X POST http://localhost:8602/publish \
    -H "Content-Type: application/json" \
    -d "{\"text\": \"$FINAL_TEXT\", \"image_url\": \"$IMAGE_URL\", \"post_type\": \"photo\"}"
fi