AGD Flow Setup Guide
Set up a publishing pipeline that collects sources, builds RAG context, runs AI steps, prepares review and publishes structured content to WordPress or your CMS.
🧭 Panel workflow overview
AGD Flow works as an AI content pipeline for web publishing: connect a site, connect an AI provider, prepare reusable rules and keywords, build a template pipeline, test the flow, then run a publishing task.
WordPress - install the
api.php file in your WordPress root, see WordPress API.OfferCMS / Custom CMS - install the client
.cms package, see Connecting a site.- 🌐 Domains — connect a site, API URL, API token, and domain-specific AI prompt.
- 🎨 External site template — after installing the client package, customize
.cms/temp: header, menu, cards, sidebar, styles, metrics, and footer. - 🤖 AI Providers — add OpenAI, Gemini, Anthropic, xAI, DeepSeek, Qwen, or a Custom OpenAI-compatible API.
- 📝 Prompts — save reusable instructions: SEO rules, style, constraints, and article format.
- 🔍 Keywords — create keyword sets for generation.
- ⚙️ Templates — build a pipeline from search, parsing, RAG, AI generation, processing, and the final article form.
- ▶️ Tasks — connect a domain, template, and keyword sets, then choose the publishing mode and start the job.
- 📝 Articles — review, create, or manually edit articles when needed.
.cms/temp template first, AI provider second, prompts and keywords third, pipeline template fourth, pipeline test fifth, production task last..cms/temp folder or the full client package .cms to an AI assistant and ask it to create a new public template, redesign it for the niche, change cards, navigation, colors, footer, static pages, and ad/analytics inserts. The important part is not to break the system template variables: $row, $posts, $post, $tophost, $incpage, $currentLang.🌐 Site connection and the .cms client
Publishing to an external site uses the client .cms package. It is a small CMS/API layer that receives publications from the panel, stores articles, images, extra fields, and returns public URLs.
Where to get the package
- Open the 🌐 Domains tab.
- Click Download client (.zip).
- Extract the archive. It contains the site package files:
index.php,classes/,pages/,temp/,vendor/, and other service files.
Option 1: install into a separate blog folder
- Create a
blogfolder in the target site's root directory. - Copy the extracted client package into that folder.
- Check that the API is available at
https://example.com/blog/api.php. - If the host returns 404, configure rewrite rules so requests inside
/blog/are handled by the clientindex.php. - In the panel domain settings, set API URL to
https://example.com/blog/api.php.
This option is safer when the domain already has a site and you do not want to mix the main site's files with the client package.
Option 2: install from the site root
- Copy the extracted client package directly into the site root.
- Use this option only if the whole site should run on OfferCMS, or if you clearly understand which files will be merged with the existing site.
- In the panel domain settings, set API URL to
https://example.com/api.php.
API Token
- On the site, open
classes/integration.php. - In
cms_get_api_token(), replace the default value with a unique long string. - In the panel, open 🌐 Domains → Add domain or edit an existing domain.
- Paste the same string into API Token.
function cms_get_api_token(): string
{
return 'your-unique-secret-token-here';
}
Authorization: Bearer API_TOKEN. If the site token and panel token do not match, publishing fails with an authorization error.🌐 Domains — site management
The Domains tab controls the sites where generated content will be published.
- Download client (.zip) — downloads the current client package for site installation. If a newer version is available, the button can show a NEW badge.
- Add domain — opens the site connection form.
- ✏️ — edit a domain.
- 🗑️ — delete a domain from the panel.
Domain fields
- Domain — the domain name, for example
example.com. - API URL — the full client API URL:
https://example.com/api.phporhttps://example.com/blog/api.php. - API Token — the secret string from
cms_get_api_token()on the site. - System prompt — rules for this specific site: language, tone, audience, topic, structure, restrictions, and publishing requirements.
The domain prompt is available inside AI steps as {{domain_aiprompt}}. This lets one template work across multiple sites with different editorial rules.
🤖 AI Providers — model connections
AI providers are configured separately from templates. This makes it easy to reuse templates with different keys, accounts, and models.
Buttons and actions
- Add provider — creates a new provider connection.
- ✏️ — edits a saved provider.
- 🧪 — sends a test request and shows HTTP code, model, response text, and troubleshooting hints.
- 🗑️ — deletes the provider. Templates using it will require attention.
Provider fields
- Provider type — Gemini, OpenAI, Anthropic, xAI, DeepSeek, Qwen, or Custom.
- Name — a readable connection name. You can keep the generated name or rename it by account/key purpose.
- Qwen region — shown only for Qwen.
- API URL — shown for Custom OpenAI-compatible APIs.
- Available models — one model per line. Template LLM steps select models from this saved list.
- 🔄 Refresh models — the button near the models field. It requests the provider model catalog, shows a preview, and lets you insert the fetched list.
- API key — the provider key.
- Active — only active providers are available for new LLM steps.
The provider card shows ID, type, capabilities, model count, active status, and possible runtime issues such as insufficient balance, invalid key, rate limit, model not found, or timeout.
📝 Prompts — global rules
Global prompts store reusable instructions used across templates: SEO rules, editorial style, source requirements, restrictions, output format, and markup rules.
- Create prompt — adds a new reusable prompt.
- Name — the name used to insert the prompt into templates.
- Content — the instruction text.
- Category — optional grouping, for example
seo,style,html,review.
Insert a prompt into an LLM step with:
{{prompt:"seo_article_rules"}}
This keeps large stable instructions in one place instead of copying them into every template.
🔍 Keywords — keyword sets
The Keywords tab stores keyword databases. One task can use one or several sets.
- Create / add set — creates a new set or adds keywords to an existing set.
- Set name — a readable name, for example
Founders CRM ENorCBD Reviews PT. - Drag & drop .txt — upload a text file with keywords.
- Manual input — paste one keyword per line.
- Rename — renames the selected set.
- Clear — removes keywords but keeps the set.
- Delete set — removes the set completely.
In a pipeline, the current keyword is available as {{keyword}}. During a task, the system runs the template for each keyword in the selected sets.
⚙️ Templates — pipeline builder
A Template is the generation scheme. It defines what should happen to a keyword: collect SERP, fetch pages, build RAG context, generate an article, extract fields, and send final article data to publishing.
Main template window elements
- Template name — the pipeline name in the list.
- Type —
New articles,Article updates, orKeywords. A template type is fixed after creation because it defines the required final form. - Step palette — split into four groups: Functions, Sources, Logic, and Result. This keeps data processing, external sources, control flow, and final workflow output separate.
- Functions — AI, RegExp, transform, RAG context, text check, and JavaScript.
- Sources — search, news, images, videos, article extraction, and HTML retrieval by URL.
- Logic — IF/ELSE, WHILE, FOR EACH, branch merge, and STOP.
- Result — New Article, Article Update, or Keywords form, depending on the template type.
- Canvas — a visual map of steps. Cards can be arranged for readability.
- − / + / 100% - canvas zoom. Zoom, viewport, and focused card are preserved while editing a step.
- Execution route - connections between cards. A step can be dragged, inserted into the middle of a flow, and assigned an explicit next target.
- Focus first error — jumps to the first step where a macro references a missing or future step.
- Show/hide JSON editor — manual JSON editing for advanced changes or template transfer.
next, on_true, on_false, on_pass, on_fail, body, and after.🧭 Template and task types
Each template has one workflow type. In Tasks, choose the same type first; the template selector then shows only compatible templates.
- New articles — input is a keyword. The final
article_formmaps title, description, Markdown, image, and extra fields into a new publication. - Article updates — input is an existing article selected from the connected site. The final
article_update_formprepares a replacement version for that exact article. - Keywords — input is one or more seed phrases. The final
keywords_formnormalizes the generated lines and adds them to a selected keyword set.
Article update safety
Article update templates preserve the article ID, slug, language, status, and original publication date. The default result is Review changes first: generation finishes, but the site is not changed until you open Task details and choose Apply or Skip.
Before applying a change, the panel checks whether the source article was edited after it entered the queue. A changed article creates a conflict instead of overwriting a manual edit. Applied changes can be rolled back while their review snapshot is still available.
🧱 Pipeline step types
🤖 AI
An LLM step sends a prompt to the selected AI provider and stores the text response as the step output.
- Step ID — a short name, for example
query_plan,article_mode,final_article. - Provider — an active connection from AI Providers.
- Type — text, text batch, image, video, or music if supported by the provider.
- Model — selected from the provider model list.
- Role —
userorsystem. - Global prompt — a saved prompt that can be inserted as a macro.
- Prompt — the main instruction text. It can use variables and outputs from previous steps.
- Variables — the button near the prompt field that inserts available macros.
- Insert cache block — wraps the stable part of a prompt in
AIPANEL_CACHE. Details and limitations are described in the caching section. - Temperature — response variability. Leave it on Auto to let the model use its default.
- Output token limit — response length limit.
- Reasoning — optional effort for models that support it. Details are in the Reasoning section.
- Additional request options — shown only for Custom API connections. Use them for provider-specific, non-system parameters.
- Batch wait seconds — shown for
text batch. Batch is cheaper but may take longer.
🔎 / 📰 / 🖼️ / 🎞️ Sources
Collector steps retrieve data from search, news, images, videos, or exact URLs.
- Search — web SERP: Google, Bing, Yandex, Yahoo.
- News — news search.
- Images — image search.
- Videos — YouTube or Yandex video.
- Article by URL — loads the specified pages and extracts their main content for later pipeline steps.
- HTML by URL — loads the original page HTML when the next step needs full markup rather than extracted article text.
- Query source — keyword, text, or a previous-step variable such as
{{keyword}}or{{step.query_plan.output}}. - SERP result limit — results per query, from 1 to 100.
Search collector output is structured: results, related, and total_items. Result rows can include query, rank, link, title, content, date, and thumb. Article by URL returns a document array containing the main text, title, and source link.
🧪 Processing / RegExp
A RegExp step extracts or replaces text fragments.
- Source — text or previous step output.
- Pattern — regular expression.
- Flags — regex modifiers.
- Extract first match — returns the first match.
- Extract all matches — returns matches separated by new lines.
- Replace matches — performs regex replacement.
🧩 Transform
Transform steps clean and prepare data between collectors and AI steps.
- Dedupe lines, Join lines, Slice chars, Shuffle array, Slice array, Strip HTML, Trim.
- Extract from JSON — takes a required fragment from JSON.
- String to array and Array to JSON — change data format for later steps.
📚 Build RAG context
A standalone function in the Functions palette. It builds a compact evidence context from documents returned by Article by URL.
RAG settings let you limit the total context size, number of sources, amount of material from one site, and size of processed fragments.
RAG collects the most useful parts of the retrieved materials into compact context for the next AI step. It does not publish content by itself.
🔀 IF/ELSE and routes
An IF/ELSE step compares left and right values and selects on_true or on_false. Operators are eq, neq, lt, lte, gt, gte, contains, not_contains, empty, not_empty, regex, truthy, and falsy.
🛡️ Text check
Select a checking service, set an acceptable threshold, and configure routes for three outcomes: passed, failed, or an error.
🔁 WHILE and FOR EACH
WHILE repeats its body while the condition is true and max_iterations is not reached. FOR EACH iterates over the items_source array. The body can use {{loop.item}}, {{loop.index}}, {{loop.iteration}}, and loop-ID variants such as {{loop.section_loop.iteration}}. The body route must return to the loop node; after completion, the after target runs.
🧩 JavaScript
A JavaScript step receives input, prior outputs in steps, safe context, and the helpers clone, jsonParse, jsonStringify, toArray, and unique. Network access, filesystem/process runtime APIs, imports, eval, async/Promise, and prototype access are blocked. Execution time, code size, and output size are limited.
⋈ Merge and ■ STOP
Merge does not execute branches in parallel. It is a shared continuation point that exposes the result of the route that actually arrived in merge.output. STOP terminates the current route and records its reason in the execution log.
📝 Article form
The final publishing step. It maps previous step outputs into article fields.
- Article title — for example
{{step.title_extract.output}}. - Description — meta description or summary.
- Image — main image URL or an image-step variable.
- Content (Markdown) — final article body.
- Extra fields — key/value pairs sent as
xfields_json.
en, pt, blog, or reviews.🔄 Article update form
The final step for an article-update template. It maps the generated title, description, Markdown, and additional fields to the existing article before changes are applied or reviewed.
🔑 Keywords form
The final step for a keywords template. It accepts an array of discovered or generated keywords and sends them to the selected keyword set.
🔗 Variables and macros
Macros connect steps together. They insert the keyword, domain, domain prompt, global prompts, and previous-step outputs.
{{keyword}}— current keyword.{{domain}}— target site domain.{{domain_aiprompt}}— domain system prompt.{{prompt:"prompt_name"}}— global prompt from Prompts.{{step.step_id.output}}— full output of a previous step.{{step.images.1.link}},{{step.search.output}}, and similar variables — structured fields when available.
Input variables by workflow type
- New articles — use
{{keyword}}, together with the domain variables above. - Article updates — use
{{article.id}},{{article.url}},{{article.slug}},{{article.lang}},{{article.title}},{{article.description}},{{article.markdown}},{{article.main_image}},{{article.xfields_json}},{{article.published_at}},{{article.updated_at}}, and{{article.age_days}}. - Keywords — use
{{seed.keyword}}and{{seed.set_name}}. Domain variables are available when a domain is selected for the task.
The Variables button in the step editor shows macros available for the current step and previous outputs.
An inserted variable is highlighted. When the same macro is used more than once, its chip shows the number of occurrences. Macros can be inserted next to each other without a forced line break, for example {{keyword}}{{domain}}.
🧭 Routing, limits, and JSON validation
Explicit targets
next- the next step for AI, collector, RegExp, transform, JavaScript, Merge, and article form.on_true/on_false- the two IF/ELSE branches.on_pass/on_fail/on_error- text-check routes.body/after- loop body and the route after completion.stoporendas a target terminates the route without a separate STOP card.
For new templates, set routes explicitly so the execution order remains clear when you add branches and loops.
Validation before save
The editor and backend reject empty or duplicate IDs, missing targets, loop bodies without a return route, and cycles that do not pass through a bounded loop. Runtime also limits the total number of executed nodes. Loops allow 1 to 1000 iterations; JavaScript allows 50-5000 ms and a bounded output size.
JSON validation
steps_json and xfields_json use a strict jsonlint-compatible validator. Errors include a readable message, line, and context; duplicate JSON keys are rejected. The steps_json root must be an array of steps, while xfields_json must be an object or array. Macros inside xfields_json are supported and validated before substitution.
🚀 Recommended SEO article pipeline
For a high-quality SEO article, it is usually better to use a research → context → generation → extraction → article form chain rather than a single AI step.
- 🤖 query_plan — AI creates 3–5 search queries from
{{keyword}}and{{domain_aiprompt}}. - 🔎 search — collector gathers SERP from
{{step.query_plan.output}}. - 🧩 clean_search — transform dedupes, limits, or extracts the needed links.
- 📄 articles — Article by URL collector fetches pages from found URLs.
- 🧩 rag_context — Build RAG context compresses fetched materials into compact context.
- 🖼️ images — image collector finds a main image or image ideas by keyword.
- 🤖 final_article — AI writes the final article using RAG, domain prompt, SEO rules, and required format.
- 🧪 title_extract / description_extract / content_extract — RegExp steps extract fields if AI returned one combined block.
- 📝 article_form — maps title, description, markdown, image, and extra fields for publishing.
For a simple template, you can use only 🤖 final_article → 📝 article_form. For more useful articles, add search, source fetching, and RAG.
<!-- AIPANEL_CACHE -->
Stable template rules:
- article structure;
- SEO requirements;
- style;
- restrictions;
- output format.
<!-- AIPANEL_CACHE_END -->
Keyword: {{keyword}}
Domain rules: {{domain_aiprompt}}
Research context: {{step.rag_context.output}} 🧠 Reasoning and thinking
Some text models can spend additional tokens on planning before they return the final text. The panel calls this Reasoning. It can improve complex instructions, structured writing, and multi-step analysis, but it also increases time and cost.
- Auto — sends no forced setting. This is the normal choice for existing templates and lets the provider choose its default behavior.
- Off — asks a compatible model to disable reasoning. Some modern models do not permit this; the step stops with an explanation instead of silently changing the request.
- Minimal, Low, Medium, High, Very high, Maximum — effort levels translated into the selected provider's API format. Start with Low or Medium and test before a large task.
- Reasoning budget — available only for API/model combinations that accept a numeric thinking budget. Leave it empty for the provider default.
The step output always contains only the final answer. Private reasoning is not passed to later steps, task logs, RAG, or publishing.
Custom API options
For a Custom API connection, Additional request options supports String, Number, Yes/No, and JSON values. This is useful for documented provider parameters such as output format or sampling options. The panel protects model, prompt, authentication, endpoint, stream, reasoning, and token-limit fields; configure those through the main LLM form.
💰 Prompt caching (AIPANEL_CACHE)
AIPANEL_CACHE marks the stable part of a prompt. The panel uses it only where the selected provider and mode support safe caching. If caching is not supported, the markers are removed before the request and the prompt text remains.
How it works by provider
- Google Gemini - paid explicit context caching through
cachedContentsis disabled in the panel. For Gemini,AIPANEL_CACHEmarkers are removed before the request and no Google cache object is created. This also applies to text batch. Google's automatic implicit caching, when applicable, works without user action. - OpenAI / xAI / DeepSeek - use the provider's automatic prompt cache when the stable prompt prefix repeats.
- Anthropic Claude - uses short ephemeral caching. It is useful for requests made close together and is not long-term storage.
- Qwen - behavior depends on the exact API and model; if caching is not applied, the request is sent as a normal prompt without markers.
When is it useful
Caching is useful when the same large stable block repeats across many requests: SEO rules, style, article structure, restrictions, response format. Savings are not guaranteed: the result depends on the provider, model, block size, and repetition rate.
How to use it
In the LLM step editor, there is an Insert cache block button. It wraps the selected part of the prompt in markers. You can also insert them manually:
<!-- AIPANEL_CACHE -->
Here are the permanent rules that do not change from request to request:
- SEO requirements, style, structure, restrictions, response format
<!-- AIPANEL_CACHE_END -->
Keyword: {{keyword}}
Domain rules: {{domain_aiprompt}}
Research context: {{step.rag_context.output}}
AIPANEL_CACHE as guaranteed savings. It only marks a stable block. Before large runs, check the billing and documentation of the selected provider.🧪 Testing a template
Before production use, test the template directly inside Templates. The available test fields depend on the template type.
- New articles — choose a connected domain, then select one keyword from its set or enter it manually.
- Article updates — choose a connected domain and one existing article. The panel checks the connection and reads the current article data for
{{article.*}}macros, but does not change the article. - Keywords — enter one seed query or select it from a set. A domain is optional for this test.
After choosing the input, click Run test.
During testing, the panel shows step progress. After execution, you can open step debug and inspect inputs, rendered prompt, provider response, regexp/transform result, RAG context, and final article structure.
▶️ Tasks — generation and publishing
A task runs one saved template against a fixed queue of inputs. Start by choosing its type: New articles, Article updates, or Keywords.
Creating a task
- Type — determines which input fields and templates are available.
- Domain — the target site.
- Template — the generation pipeline. Templates with Needs attention cannot run until provider or macro issues are fixed.
- Keyword sets — choose one or more sets. If empty, the task runs without keywords.
- Publishing mode — publish all, only N articles, or scheduled mode.
- Max articles — used in limited mode.
- Articles per period / Period hours — used in scheduled mode.
- Language / URL section — forced URL segment, for example
en,pt,blog,reviews. - Skip used keywords — prevents duplicate generation for already processed keywords.
- Notify Google and Notify Bing — send notifications after successful publication.
Inputs for each type
- New articles — select one or more keyword sets, then choose immediate, limited, or scheduled publication.
- Article updates — select posts manually with search and pagination, select posts older than a chosen number of months, or use a publication-date range. You can additionally filter by language and status, and set the maximum number of posts. The queue keeps the selected IDs, but the full article is fetched again immediately before processing.
- Keywords — enter seed phrases manually or use one or more existing keyword sets. Select an existing destination set or enter a name for a new one.
Statistics and statuses
- Total, Running, Completed, Failed — task statistics.
- Slots — available, used, and free execution slots.
- Lacking — tasks waiting for free slots.
- Pending, Running, Waiting for batch, Awaiting review, Completed, Failed, Paused, Cancelled — task states.
Task actions
- Details — shows progress, results, URLs, errors, search notifications, and execution log. For Article updates it also shows the source and candidate versions, with Apply, Skip, and Rollback actions where available.
- Edit — changes task settings.
- Pause — stops further execution.
- Resume/restart — continues or restarts when a slot is available.
- Cancel — cancels execution.
- Delete task — removes the task and related data.
📝 Articles — manual article management
The Articles tab is used for reviewing and manually editing materials. It is useful when you need to create an article without a pipeline or inspect existing records.
- Domain filter — shows articles for one site.
- Status filter — draft, ready for review, or published.
- Refresh — reloads the list.
- Create article — opens the manual article form.
Article fields
- Status — draft, ready for review, or published.
- Language — for example
en,pt,ru. - Slug — article URL slug.
- Heading — article H1/title.
- Description — meta description or summary.
- Content — Markdown content.
- Main image — file upload, image URL, or base64.
- Extra fields — custom fields saved as JSON.
🎨 .cms/temp — public site template
The .cms/temp folder is not the pipeline template from the panel's Templates tab. It is the public site template. This is where you customize how published articles look for visitors: header, menu, home/listing page, post cards, article page, sidebar, footer, colors, CSS, metrics, and advertising inserts.
.cms/temp folder controls the public design. These are two different layers, and both must be configured.Main template files
temp/temp.php— the main HTML template. It usually controls<head>, title/description, canonical, JSON-LD, header, navigation, main container, article output, sidebar output, and footer.temp/css/style.css— site styles: colors, container width, cards, navigation, typography, responsiveness, buttons, pagination, and sidebar.temp/short.php— the small article card used on the home page, language pages, listing pages, and pagination pages.temp/sidebar.php— the related/random article card used in the sidebar on article pages.temp/metric.php— analytics, advertising scripts, pixels, counters, and other inserts loaded by the main template.pages/— static pages:about,contact,privacy,terms,cookies,disclaimer,legal. These should also be adapted for the specific site.
What must be customized for each site
- Open
.cms/temp/temp.php. - Replace the site name, for example
$siteName,$defaultTitle, and$siteTagline. - Review navigation links: Home, Blog, About, Contact, Privacy, Terms. Remove what is unnecessary or add the required sections.
- Review the hero text on the home/listing page. It must match the site's niche, language, and audience.
- Review the footer: project name, description, contacts, and legal links.
- Open
.cms/temp/css/style.cssand customize the palette in the:rootblock: background, brand color, accent color, container width, radius, and spacing. - Open
.cms/temp/metric.phpand replace or remove foreign analytics IDs, ad codes, and pixels. Do not leave someone else's Google AdSense, Yandex.Metrika, or any other third-party IDs. - Open files in
.cms/pages/and replace the default texts with pages for the specific project.
Configuring the Telegram contact form
contact page is displayed immediately, but sending is disabled by default. Until the site owner enters their own Telegram bot details, visitors receive a message that the form is not configured and no message is sent anywhere.- Create your own bot through
@BotFatherand obtain its bot token. - Send any message to the bot, then obtain the target private chat or group ID through Telegram's
getUpdatesmethod. - Open
.cms/classes/send.php. - Enter the token in
$telegram_api_tokenand the chat ID in$chat_id. - Open the site's
contactpage and submit a test message. It must appear only in the Telegram chat you configured.
$telegram_api_token = 'YOUR_TELEGRAM_BOT_TOKEN';
$chat_id = 'YOUR_TELEGRAM_CHAT_ID';
pages/, temp/, JavaScript, or a public repository. If the token is exposed, rotate it through @BotFather. Disclose the form processing and transfer of the visitor's name, email, message, IP address, and domain to Telegram in the site's privacy policy.Data available inside the template
$row['title']— current page or article title.$row['description']— meta description or short description.$row['html_content']— final article HTML or the list of cards on the home/listing page.$row['image_url']— the main article image, if it was provided or found by the system.$row['date']/$row['public_date']— publication date, when available.$row['sidebar']— sidebar HTML with random/related posts, when built.$posts— the post list on the home page, language page, or pagination page.$post— one post insideshort.phporsidebar.php.$tophost,$hostname,$incpage,$currentLang,$pagePath— service variables for URL, domain, language, and current page.
short.php or sidebar.php, remember that they use the $post variable. When editing temp.php, the main page variable is $row.How the engine uses temp files
- For the home page and pagination pages, the engine collects published articles, renders each item through
short.php, then outputs the result throughtemp.php. - For an article page, the engine loads the article, prepares
$row['html_content'], builds$row['sidebar']throughsidebar.php, then includestemp.php. - If the template folder contains
fullpage.php, it can be used for specific internal pages instead of the standardtemp.php. metric.phpis usually included fromtemp.php, so analytics and advertising scripts can be kept in one separate file.
Minimum checklist before publishing
- The site home page opens.
- An article opens by its public URL.
- Images do not break the mobile layout.
- The menu links point to existing pages.
- The footer has no foreign brand, foreign links, or placeholder text.
metric.phphas no foreign ad network or analytics IDs..cms/classes/send.phpcontains credentials for the site's own Telegram bot, and a test message has been received.- The page source contains proper
title,meta description, canonical, and, when needed, JSON-LD. privacy,terms,contact, andaboutpages are filled for the specific site.
WordPress API — Connecting the Bridge
To publish content to WordPress sites, a dedicated api.php bridge is used. The panel sends publications, checks URL uniqueness, updates posts, and uploads media files through this script.
Installation and Setup
- Download the WordPress bridge script: in the documentation header or domain settings, click Download WordPress Client (auth required).
- Copy the downloaded
api.phpfile to the root directory of your WordPress installation (wherewp-load.phpis located). - Open your WordPress
wp-config.phpfile and define a secure API token:define('AIPANEL_API_TOKEN', 'your-long-secure-token'); - In the AGD Flow control panel, when adding a domain, specify:
- API URL:
https://yourdomain.com/api.php - API Token: the exact token defined in
wp-config.php
- API URL:
Bridge Features
- Supported Actions: connection test (
ping), publish post (publish_post), edit post (edit_post), delete post (delete_post), and slug availability check (check_slug). - SEO Plugin Integration: automatically writes SEO title and meta description for Yoast SEO (
_yoast_wpseo_title,_yoast_wpseo_metadesc) and Rank Math (rank_math_title,rank_math_description). - Categories and Tags: supports passing categories and tags arrays inside the
xfields_jsonfield (structure:{"categories": ["Category 1", "Category 2"], "tags": ["Tag 1", "Tag 2"]}). If a category or tag does not exist, it will be created automatically. - Custom Post Types: by default, posts are published under the standard
posttype. You can override this by passing the post type insidexfields_json(e.g.{"post_type": "product"}or{"post_type": "page"}). - Images: automatically downloads the featured image from a URL or decodes a base64 image string, inserts it into the WordPress Media Library, and sets it as the post's Featured Image.
📋 Example JSON Pipelines
Here are ready-to-use pipeline templates in JSON format that you can import using the Show/hide JSON editor button in the ⚙️ Templates tab.
1. Simple SEO Article
A minimal template: sends a prompt to the AI, extracts title, description, and content tags using RegExp, and maps them to the publication form.
[
{
"id": "draft_article",
"type": "llm",
"provider_id": 0,
"ai_capability": "text",
"model": "",
"prompt_role": "user",
"prompt_text": "Keyword: {{keyword}}\nDomain: {{domain}}\n\n{{domain_aiprompt}}\n\n\n{{prompt:\"seo_article_rules\"}}\n{{prompt:\"article_output_contract\"}}\n\nWrite a practical SEO article for the keyword. Vary the number of sections naturally; include FAQ only when it helps the topic.\n",
"temperature": 0.4,
"max_tokens": 4096,
"output_key": "output"
},
{
"id": "extract_title",
"type": "regexp",
"mode": "extract",
"input_source": "{{step.draft_article.output}}",
"pattern": "\\s*(.*?)\\s* ",
"flags": "si",
"output_key": "output"
},
{
"id": "extract_description",
"type": "regexp",
"mode": "extract",
"input_source": "{{step.draft_article.output}}",
"pattern": "\\s*(.*?)\\s* ",
"flags": "si",
"output_key": "output"
},
{
"id": "extract_content",
"type": "regexp",
"mode": "extract",
"input_source": "{{step.draft_article.output}}",
"pattern": "\\s*(.*?)\\s* ",
"flags": "si",
"output_key": "output"
},
{
"id": "article",
"type": "article_form",
"title": "{{step.extract_title.output}}",
"description": "{{step.extract_description.output}}",
"markdown": "{{step.extract_content.output}}",
"lang": "{{domain_lang}}",
"slug": "",
"main_image": "",
"xfields_json": "[]",
"public": 1
}
]
2. Keyword Expansion
A "Keywords" type template to generate related search topics based on a seed keyword.
[
{
"id": "expand_keywords",
"type": "llm",
"provider_id": 0,
"ai_capability": "text",
"model": "",
"prompt_role": "user",
"prompt_text": "Seed keyword: {{keyword}}\nDomain: {{domain}}\n\n{{domain_aiprompt}}\n\n\nGenerate 20 useful long-tail keyword ideas for content planning. Keep them specific, natural, and suitable for article topics.\nReturn one keyword per line, no numbering, no commentary.\n",
"temperature": 0.35,
"max_tokens": 2048,
"output_key": "output"
},
{
"id": "keywords",
"type": "transform",
"operation": "split_to_array",
"arg1": "{{step.expand_keywords.output}}",
"delimiter": "\n",
"array_limit": 20,
"output_key": "output"
}
]
3. Advanced Affiliate Template with Search & RAG
A complex pipeline for product reviews: queries Google, downloads target pages, builds RAG context, and feeds it to the AI step.
[
{
"id": "affiliate_queries",
"type": "llm",
"provider_id": 0,
"ai_capability": "text_batch",
"model": "",
"prompt_role": "user",
"prompt_text": "Affiliate review idea: {{keyword}}\nDomain: {{domain}}\n\n{{domain_aiprompt}}\n\n\nGenerate 5 focused web search queries that support an affiliate review. Return one query per line only.\n",
"temperature": 0.2,
"max_tokens": 1024,
"output_key": "output"
},
{
"id": "affiliate_split_queries",
"type": "transform",
"operation": "split_to_array",
"arg1": "{{step.affiliate_queries.output}}",
"delimiter": "\n",
"array_limit": 5,
"output_key": "output"
},
{
"id": "affiliate_search",
"type": "collector",
"channel": "web",
"engine": "google",
"query_source": "{{step.affiliate_split_queries.output}}",
"serp_limit": 5,
"output_key": "output",
"concurrency": 6
},
{
"id": "affiliate_slice_search_results",
"type": "transform",
"operation": "slice_array",
"arg1": "{{step.affiliate_search.output}}",
"array_limit": 3,
"output_key": "output"
},
{
"id": "affiliate_html",
"type": "collector",
"channel": "article",
"engine": "article",
"query_source": "{{step.affiliate_slice_search_results.output}}",
"output_key": "output"
},
{
"id": "affiliate_rag",
"type": "transform",
"operation": "rag_builder",
"arg1": "{{step.affiliate_html.output}}",
"array_limit": 5,
"output_key": "output",
"max_total_chars": 16000,
"max_sources": 10,
"chunk_size": 900
},
{
"id": "affiliate_generate",
"type": "llm",
"provider_id": 0,
"model": "",
"prompt_role": "user",
"prompt_text": "Keyword: {{keyword}}\nDomain: {{domain}}\nResearch context:\n{{step.affiliate_rag.output}}\n\n{{domain_aiprompt}}\n\nWrite a practical affiliate review. Format output inside tags:\n<TITLE>[title]</TITLE>\n<DESCRIPTION>[description]</DESCRIPTION>\n<CONTENT>[content]</CONTENT>",
"temperature": 0.45,
"max_tokens": 6144,
"output_key": "output"
},
{
"id": "affiliate_extract_title",
"type": "regexp",
"mode": "extract",
"input_source": "{{step.affiliate_generate.output}}",
"pattern": "\\s*(.*?)\\s* ",
"flags": "si",
"output_key": "output"
},
{
"id": "affiliate_extract_description",
"type": "regexp",
"mode": "extract",
"input_source": "{{step.affiliate_generate.output}}",
"pattern": "\\s*(.*?)\\s* ",
"flags": "si",
"output_key": "output"
},
{
"id": "affiliate_extract_content",
"type": "regexp",
"mode": "extract",
"input_source": "{{step.affiliate_generate.output}}",
"pattern": "\\s*(.*?)\\s* ",
"flags": "si",
"output_key": "output"
},
{
"id": "affiliate_article",
"type": "article_form",
"title": "{{step.affiliate_extract_title.output}}",
"description": "{{step.affiliate_extract_description.output}}",
"markdown": "{{step.affiliate_extract_content.output}}",
"lang": "{{domain_lang}}",
"slug": "",
"main_image": "",
"xfields_json": "[]",
"public": 1
}
]
4. Research + RAG Article
A research-backed article pipeline: generates search queries, queries Google, downloads source pages, builds RAG context, and writes a grounded article.
[
{
"id": "research_queries",
"type": "llm",
"provider_id": 0,
"ai_capability": "text_batch",
"model": "",
"prompt_role": "user",
"prompt_text": "Topic keyword: {{keyword}}\nDomain: {{domain}}\n\n{{domain_aiprompt}}\n\n\nGenerate 6 focused web search queries that support an informative article about the keyword. Return one query per line only, no numbering, no commentary.\n",
"temperature": 0.2,
"max_tokens": 1024,
"output_key": "output"
},
{
"id": "research_split_queries",
"type": "transform",
"operation": "split_to_array",
"arg1": "{{step.research_queries.output}}",
"delimiter": "\n",
"array_limit": 6,
"output_key": "output"
},
{
"id": "research_search",
"type": "collector",
"channel": "web",
"engine": "google",
"query_source": "{{step.research_split_queries.output}}",
"serp_limit": 5,
"output_key": "output",
"concurrency": 6
},
{
"id": "research_slice",
"type": "transform",
"operation": "slice_array",
"arg1": "{{step.research_search.output}}",
"array_limit": 4,
"output_key": "output"
},
{
"id": "research_html",
"type": "collector",
"channel": "article",
"engine": "article",
"query_source": "{{step.research_slice.output}}",
"output_key": "output"
},
{
"id": "research_rag",
"type": "transform",
"operation": "rag_builder",
"arg1": "{{step.research_html.output}}",
"array_limit": 5,
"output_key": "output",
"max_total_chars": 16000,
"max_sources": 10,
"chunk_size": 900
},
{
"id": "research_writer",
"type": "llm",
"provider_id": 0,
"model": "",
"prompt_role": "user",
"prompt_text": "Keyword: {{keyword}}\nDomain: {{domain}}\nResearch context:\n{{step.research_rag.output}}\n\n{{article_structure_hint}}\n\n{{domain_aiprompt}}\n\n\nWrite a practical, research-backed article using the provided context. Ground claims in the sources; do not invent facts. Format output inside tags:\n<TITLE>[title]</TITLE>\n<DESCRIPTION>[meta description]</DESCRIPTION>\n<CONTENT>[full article markdown]</CONTENT>\n",
"temperature": 0.45,
"max_tokens": 6144,
"output_key": "output"
},
{
"id": "research_extract_title",
"type": "regexp",
"mode": "extract",
"input_source": "{{step.research_writer.output}}",
"pattern": "\\s*(.*?)\\s* ",
"flags": "si",
"output_key": "output"
},
{
"id": "research_extract_description",
"type": "regexp",
"mode": "extract",
"input_source": "{{step.research_writer.output}}",
"pattern": "\\s*(.*?)\\s* ",
"flags": "si",
"output_key": "output"
},
{
"id": "research_extract_content",
"type": "regexp",
"mode": "extract",
"input_source": "{{step.research_writer.output}}",
"pattern": "\\s*(.*?)\\s* ",
"flags": "si",
"output_key": "output"
},
{
"id": "research_article",
"type": "article_form",
"title": "{{step.research_extract_title.output}}",
"description": "{{step.research_extract_description.output}}",
"markdown": "{{step.research_extract_content.output}}",
"lang": "en",
"slug": "",
"main_image": "",
"xfields_json": "[]",
"public": 1
}
]
5. Multilingual Publishing
Publishes one article in two languages from the same source material: one English writer step and one Russian writer step, each producing its own article form. Adapt the second language, prompts and domain rules per project.
[
{
"id": "shared_draft",
"type": "llm",
"provider_id": 0,
"ai_capability": "text",
"model": "",
"prompt_role": "user",
"prompt_text": "Keyword: {{keyword}}\nDomain: {{domain}}\n\n{{domain_aiprompt}}\n\n\nOutline a concise article structure for the keyword: an intro, 3-5 H2 sections, and an FAQ block only if it helps the topic. Return only the outline as a numbered list.\n",
"temperature": 0.3,
"max_tokens": 1024,
"output_key": "output"
},
{
"id": "writer_en",
"type": "llm",
"provider_id": 0,
"model": "",
"prompt_role": "user",
"prompt_text": "Keyword: {{keyword}}\nDomain: {{domain}}\nOutline:\n{{step.shared_draft.output}}\n\n{{domain_aiprompt}}\n\n\nWrite a practical English article following the outline. Format output inside tags:\n<TITLE>[title]</TITLE>\n<DESCRIPTION>[meta description]</DESCRIPTION>\n<CONTENT>[full article markdown]</CONTENT>\n",
"temperature": 0.45,
"max_tokens": 4096,
"output_key": "output"
},
{
"id": "extract_title_en",
"type": "regexp",
"mode": "extract",
"input_source": "{{step.writer_en.output}}",
"pattern": "\\s*(.*?)\\s* ",
"flags": "si",
"output_key": "output"
},
{
"id": "extract_description_en",
"type": "regexp",
"mode": "extract",
"input_source": "{{step.writer_en.output}}",
"pattern": "\\s*(.*?)\\s* ",
"flags": "si",
"output_key": "output"
},
{
"id": "extract_content_en",
"type": "regexp",
"mode": "extract",
"input_source": "{{step.writer_en.output}}",
"pattern": "\\s*(.*?)\\s* ",
"flags": "si",
"output_key": "output"
},
{
"id": "article_en",
"type": "article_form",
"title": "{{step.extract_title_en.output}}",
"description": "{{step.extract_description_en.output}}",
"markdown": "{{step.extract_content_en.output}}",
"lang": "en",
"slug": "",
"main_image": "",
"xfields_json": "[]",
"public": 1
},
{
"id": "writer_ru",
"type": "llm",
"provider_id": 0,
"model": "",
"prompt_role": "user",
"prompt_text": "Keyword: {{keyword}}\nDomain: {{domain}}\nOutline:\n{{step.shared_draft.output}}\n\n{{domain_aiprompt}}\n\n\nНапишите практическую статью на русском языке по этой структуре. Оформите вывод внутри тегов:\n<TITLE>[заголовок]</TITLE>\n<DESCRIPTION>[мета-описание]</DESCRIPTION>\n<CONTENT>[полный текст статьи в markdown]</CONTENT>\n",
"temperature": 0.45,
"max_tokens": 4096,
"output_key": "output"
},
{
"id": "extract_title_ru",
"type": "regexp",
"mode": "extract",
"input_source": "{{step.writer_ru.output}}",
"pattern": "\\s*(.*?)\\s* ",
"flags": "si",
"output_key": "output"
},
{
"id": "extract_description_ru",
"type": "regexp",
"mode": "extract",
"input_source": "{{step.writer_ru.output}}",
"pattern": "\\s*(.*?)\\s* ",
"flags": "si",
"output_key": "output"
},
{
"id": "extract_content_ru",
"type": "regexp",
"mode": "extract",
"input_source": "{{step.writer_ru.output}}",
"pattern": "\\s*(.*?)\\s* ",
"flags": "si",
"output_key": "output"
},
{
"id": "article_ru",
"type": "article_form",
"title": "{{step.extract_title_ru.output}}",
"description": "{{step.extract_description_ru.output}}",
"markdown": "{{step.extract_content_ru.output}}",
"lang": "ru",
"slug": "",
"main_image": "",
"xfields_json": "[]",
"public": 1
}
]
6. WordPress Product Post
Generates a WooCommerce-style product entry and publishes it as a product custom post type with categories, tags, price, SKU, SEO metadata and a featured image. The WordPress bridge reads these fields from xfields_json.
[
{
"id": "product_brief",
"type": "llm",
"provider_id": 0,
"ai_capability": "text",
"model": "",
"prompt_role": "user",
"prompt_text": "Product keyword: {{keyword}}\nDomain: {{domain}}\n\n{{domain_aiprompt}}\n\n\nGenerate a structured product description for the keyword. Return valid JSON only (no markdown fences) with these keys:\n{\"title\": \"\", \"short_description\": \"\", \"description\": \"\", \"price\": \"\", \"sku\": \"\", \"categories\": [\"\"], \"tags\": [\"\"]}\n",
"temperature": 0.4,
"max_tokens": 3072,
"output_key": "output"
},
{
"id": "product_title",
"type": "transform",
"operation": "json_path",
"arg1": "{{step.product_brief.output}}",
"output_key": "title"
},
{
"id": "product_description",
"type": "transform",
"operation": "json_path",
"arg1": "{{step.product_brief.output}}",
"output_key": "description"
},
{
"id": "product_short",
"type": "transform",
"operation": "json_path",
"arg1": "{{step.product_brief.output}}",
"output_key": "short_description"
},
{
"id": "product_xfields",
"type": "transform",
"operation": "json_path",
"arg1": "{{step.product_brief.output}}",
"output_key": "xfields"
},
{
"id": "product_article",
"type": "article_form",
"title": "{{step.product_title.output}}",
"description": "{{step.product_short.output}}",
"markdown": "{{step.product_description.output}}",
"lang": "en",
"slug": "",
"main_image": "",
"xfields_json": "{\"post_type\":\"product\",\"categories\":{{step.product_xfields.output.categories}},\"tags\":{{step.product_xfields.output.tags}},\"meta\":{\"_regular_price\":\"{{step.product_xfields.output.price}}\",\"_sku\":\"{{step.product_xfields.output.sku}}\",\"_visibility\":\"visible\"},\"seo\":{\"title\":\"{{step.product_title.output}}\",\"description\":\"{{step.product_short.output}}\"}}",
"public": 1
}
]
7. Ready-to-use pipeline examples
The Templates section includes ready-to-use pipeline examples. Open them, study them, copy them, and adapt them to your workflow.
| Template | What it demonstrates | Core route |
|---|---|---|
| Example - Graph IF ELSE and Merge | IF/ELSE, two AI branches, Merge, JavaScript, RegExp, and article form. | condition → writer → merge → script → article_form |
| Example - AI Check Rewrite Loop | Strict AI threshold, three check routes, a bounded WHILE loop, and STOP. | draft → check → loop/rewrite → check → article |
| Example - FOR EACH Sections | Array preparation, loop variables, per-item processing, and result accumulation. | script → for_each → AI → accumulator → loop → article |
| Example - Source RAG Quality Gate | Search, URL source loading, RAG context, IF, and AI quality gate. | search → article → RAG → condition → writer → check → article |
IF/ELSE + Merge
A regex condition looks for comparison intent in the keyword. Only one branch executes. Both branches route into Merge, then JavaScript returns input.output from the merge object. Merge is a convergence point, not a parallel executor.
{
"id": "choose_article_mode",
"type": "condition",
"left": "{{keyword}}",
"operator": "regex",
"right": "~\\b(vs|versus|compare|comparison|сравнение|против)\\b~iu",
"on_true": "comparison_writer",
"on_false": "guide_writer"
}
{
"id": "branch_merge",
"type": "merge",
"next": "unwrap_branch"
}
{
"id": "unwrap_branch",
"type": "script",
"input_source": "{{step.branch_merge.output}}",
"code": "return input && typeof input === \"object\" && \"output\" in input ? input.output : input;",
"next": "branch_title"
}
AI Check + bounded rewrite loop
After every check, select_candidate selects the latest rewrite when one exists. On failure, the loop allows no more than three rewrites. The body returns through rewrite_article → select_candidate → ai_quality → rewrite_loop, making the cycle valid and bounded. Exhaustion and service failure use separate STOP routes.
{
"id": "ai_quality",
"type": "ai_check",
"input_source": "{{step.select_candidate.output}}",
"threshold_ai_percent": 30,
"comparison": "less_than",
"on_pass": "approved_title",
"on_fail": "rewrite_loop",
"on_error": "detector_error"
}
{
"id": "rewrite_loop",
"type": "loop",
"mode": "while",
"left": "{{step.ai_quality.output.passed}}",
"operator": "falsy",
"body": "rewrite_article",
"after": "rewrite_exhausted",
"max_iterations": 3
}
FOR EACH + result accumulation
prepare_sections returns an array. AI writes one section for the current {{loop.item}}. Because a repeatedly executed step normally retains only its latest output, collect_sections explicitly appends the new text to its previous array. After the loop, render_markdown joins that array.
{
"id": "section_loop",
"type": "loop",
"mode": "for_each",
"items_source": "{{step.prepare_sections.output}}",
"body": "write_section",
"after": "render_markdown",
"max_iterations": 10
}
{
"id": "collect_sections",
"type": "script",
"input_source": "{{step.write_section.output}}",
"code": "const previous = Array.isArray(steps.collect_sections) ? steps.collect_sections : [];\nreturn previous.concat([input]);",
"next": "section_loop"
}
Sources + RAG + quality gate
Search results are passed to Article by URL, then to RAG. The condition prevents the writer from running with an empty context. The writer receives prepared context, and the generated content then passes through the text check.
{
"id": "article_extract",
"type": "collector",
"channel": "article",
"engine": "article",
"query_source": "{{step.evidence_search.output}}",
"next": "evidence_rag"
}
{
"id": "evidence_rag",
"type": "transform",
"operation": "rag_builder",
"arg1": "{{step.article_extract.output}}",
"max_total_chars": 15000,
"max_per_host": 2,
"max_sources": 8,
"chunk_size": 1000,
"next": "context_gate"
}
Complete JSON for all four examples
The complete JSON arrays below can be copied into the template JSON editor and adapted to your workflow.
Example - Graph IF ELSE and Merge
Complete JSON: IF/ELSE, two branches, Merge, JavaScript, and article form.
[
{
"id": "choose_article_mode",
"type": "condition",
"left": "{{keyword}}",
"operator": "regex",
"right": "~\\b(vs|versus|compare|comparison|сравнение|против)\\b~iu",
"on_true": "comparison_writer",
"on_false": "guide_writer",
"output_key": "output"
},
{
"id": "comparison_writer",
"type": "llm",
"provider_id": 2,
"ai_capability": "text",
"model": "",
"prompt_role": "user",
"prompt_text": "Keyword: {{keyword}}\nDomain: {{domain}}\n\n{{domain_aiprompt}}\n\nWrite a balanced comparison article. Return exactly:\n<TITLE>Title</TITLE>\n<DESCRIPTION>Meta description</DESCRIPTION>\n<CONTENT>Markdown article</CONTENT>",
"temperature": 0.4,
"max_tokens": 4096,
"output_key": "output",
"next": "branch_merge"
},
{
"id": "guide_writer",
"type": "llm",
"provider_id": 2,
"ai_capability": "text",
"model": "",
"prompt_role": "user",
"prompt_text": "Keyword: {{keyword}}\nDomain: {{domain}}\n\n{{domain_aiprompt}}\n\nWrite a practical how-to guide. Return exactly:\n<TITLE>Title</TITLE>\n<DESCRIPTION>Meta description</DESCRIPTION>\n<CONTENT>Markdown article</CONTENT>",
"temperature": 0.4,
"max_tokens": 4096,
"output_key": "output",
"next": "branch_merge"
},
{
"id": "branch_merge",
"type": "merge",
"output_key": "output",
"next": "unwrap_branch"
},
{
"id": "unwrap_branch",
"type": "script",
"language": "javascript",
"input_source": "{{step.branch_merge.output}}",
"code": "return input && typeof input === \"object\" && \"output\" in input ? input.output : input;",
"timeout_ms": 1000,
"max_output_bytes": 262144,
"output_key": "output",
"next": "branch_title"
},
{
"id": "branch_title",
"type": "regexp",
"mode": "extract_one",
"input_source": "{{step.unwrap_branch.output}}",
"pattern": "<TITLE>\\s*(.*?)\\s*</TITLE>",
"flags": "si",
"output_key": "output",
"next": "branch_description"
},
{
"id": "branch_description",
"type": "regexp",
"mode": "extract_one",
"input_source": "{{step.unwrap_branch.output}}",
"pattern": "<DESCRIPTION>\\s*(.*?)\\s*</DESCRIPTION>",
"flags": "si",
"output_key": "output",
"next": "branch_content"
},
{
"id": "branch_content",
"type": "regexp",
"mode": "extract_one",
"input_source": "{{step.unwrap_branch.output}}",
"pattern": "<CONTENT>\\s*(.*?)\\s*</CONTENT>",
"flags": "si",
"output_key": "output",
"next": "branch_article"
},
{
"id": "branch_article",
"type": "article_form",
"title": "{{step.branch_title.output}}",
"description": "{{step.branch_description.output}}",
"markdown": "{{step.branch_content.output}}",
"lang": "{{domain_lang}}",
"slug": "",
"main_image": "",
"xfields_json": "{}",
"public": 1,
"output_key": "article_data",
"next": "end"
}
]Example - AI Check Rewrite Loop
Complete JSON: AI text check, bounded WHILE, rewrite attempts, and STOP routes.
[
{
"id": "initial_draft",
"type": "llm",
"provider_id": 2,
"ai_capability": "text",
"model": "",
"prompt_role": "user",
"prompt_text": "Keyword: {{keyword}}\nDomain: {{domain}}\n\n{{domain_aiprompt}}\n\nWrite a useful article in a natural editorial style. Return exactly:\n<TITLE>Title</TITLE>\n<DESCRIPTION>Meta description</DESCRIPTION>\n<CONTENT>Markdown article</CONTENT>",
"temperature": 0.65,
"max_tokens": 4096,
"output_key": "output",
"next": "select_candidate"
},
{
"id": "select_candidate",
"type": "script",
"language": "javascript",
"input_source": "{{step.initial_draft.output}}",
"code": "return steps.rewrite_article || input;",
"timeout_ms": 1000,
"max_output_bytes": 262144,
"output_key": "output",
"next": "ai_quality"
},
{
"id": "ai_quality",
"type": "ai_check",
"detector": "service_1",
"input_source": "{{step.select_candidate.output}}",
"threshold_ai_percent": 30,
"comparison": "less_than",
"on_pass": "approved_title",
"on_fail": "rewrite_loop",
"on_error": "detector_error",
"output_key": "output"
},
{
"id": "rewrite_loop",
"type": "loop",
"mode": "while",
"left": "{{step.ai_quality.output.passed}}",
"operator": "falsy",
"right": "",
"body": "rewrite_article",
"after": "rewrite_exhausted",
"max_iterations": 3,
"output_key": "output"
},
{
"id": "rewrite_article",
"type": "llm",
"provider_id": 2,
"ai_capability": "text",
"model": "",
"prompt_role": "user",
"prompt_text": "Rewrite the article below in a more natural, specific and varied editorial style. Preserve factual meaning and the TITLE, DESCRIPTION and CONTENT tags.\n\nCurrent article:\n{{step.select_candidate.output}}\n\nRewrite attempt: {{loop.iteration}} of 3",
"temperature": 0.85,
"max_tokens": 4096,
"output_key": "output",
"next": "select_candidate"
},
{
"id": "approved_title",
"type": "regexp",
"mode": "extract_one",
"input_source": "{{step.select_candidate.output}}",
"pattern": "<TITLE>\\s*(.*?)\\s*</TITLE>",
"flags": "si",
"output_key": "output",
"next": "approved_description"
},
{
"id": "approved_description",
"type": "regexp",
"mode": "extract_one",
"input_source": "{{step.select_candidate.output}}",
"pattern": "<DESCRIPTION>\\s*(.*?)\\s*</DESCRIPTION>",
"flags": "si",
"output_key": "output",
"next": "approved_content"
},
{
"id": "approved_content",
"type": "regexp",
"mode": "extract_one",
"input_source": "{{step.select_candidate.output}}",
"pattern": "<CONTENT>\\s*(.*?)\\s*</CONTENT>",
"flags": "si",
"output_key": "output",
"next": "approved_article"
},
{
"id": "rewrite_exhausted",
"type": "stop",
"reason": "Text did not pass the AI threshold after 3 rewrites. Last AI score: {{step.ai_quality.output.ai_percent}}%",
"output_key": "output"
},
{
"id": "detector_error",
"type": "stop",
"reason": "AI text-check service failed: {{step.ai_quality.output.error}}",
"output_key": "output"
},
{
"id": "approved_article",
"type": "article_form",
"title": "{{step.approved_title.output}}",
"description": "{{step.approved_description.output}}",
"markdown": "{{step.approved_content.output}}",
"lang": "{{domain_lang}}",
"slug": "",
"main_image": "",
"xfields_json": "{\"ai_percent\":\"{{step.ai_quality.output.ai_percent}}\"}",
"public": 1,
"output_key": "article_data",
"next": "end"
}
]Example - FOR EACH Sections
Complete JSON: FOR EACH, loop variables, and JavaScript result accumulation.
[
{
"id": "prepare_sections",
"type": "script",
"language": "javascript",
"input_source": "{{keyword}}",
"code": "return [\"Overview and definitions: \" + input, \"Practical implementation: \" + input, \"Common mistakes and checks: \" + input];",
"timeout_ms": 1000,
"max_output_bytes": 262144,
"output_key": "output",
"next": "section_loop"
},
{
"id": "section_loop",
"type": "loop",
"mode": "for_each",
"items_source": "{{step.prepare_sections.output}}",
"body": "write_section",
"after": "render_markdown",
"max_iterations": 10,
"output_key": "output"
},
{
"id": "write_section",
"type": "llm",
"provider_id": 2,
"ai_capability": "text",
"model": "",
"prompt_role": "user",
"prompt_text": "Keyword: {{keyword}}\nSection request: {{loop.item}}\nZero-based index: {{loop.index}}\nIteration: {{loop.section_loop.iteration}}\n\n{{domain_aiprompt}}\n\nWrite only this article section in Markdown. Start with an H2 heading and avoid repeating other sections.",
"temperature": 0.45,
"max_tokens": 1536,
"output_key": "output",
"next": "collect_sections"
},
{
"id": "collect_sections",
"type": "script",
"language": "javascript",
"input_source": "{{step.write_section.output}}",
"code": "const previous = Array.isArray(steps.collect_sections) ? steps.collect_sections : [];\nreturn previous.concat([input]);",
"timeout_ms": 1000,
"max_output_bytes": 262144,
"output_key": "output",
"next": "section_loop"
},
{
"id": "render_markdown",
"type": "script",
"language": "javascript",
"input_source": "{{step.collect_sections.output}}",
"code": "const sections = Array.isArray(input) ? input : [];\nreturn sections.join(\"\\n\\n\");",
"timeout_ms": 1000,
"max_output_bytes": 262144,
"output_key": "output",
"next": "sections_article"
},
{
"id": "sections_article",
"type": "article_form",
"title": "Guide: {{keyword}}",
"description": "A structured practical guide about {{keyword}}.",
"markdown": "{{step.render_markdown.output}}",
"lang": "{{domain_lang}}",
"slug": "",
"main_image": "",
"xfields_json": "{\"sections\":{{step.collect_sections.output}}}",
"public": 1,
"output_key": "article_data",
"next": "end"
}
]Example - Source RAG Quality Gate
Complete JSON: sources, RAG context, IF, and AI quality gate.
[
{
"id": "evidence_search",
"type": "collector",
"channel": "web",
"engine": "google",
"query_source": "{{keyword}}",
"serp_limit": 8,
"output_key": "output",
"next": "article_extract"
},
{
"id": "article_extract",
"type": "collector",
"channel": "article",
"engine": "article",
"query_source": "{{step.evidence_search.output}}",
"max_total_time_sec": 300,
"output_key": "output",
"next": "evidence_rag"
},
{
"id": "evidence_rag",
"type": "transform",
"operation": "rag_builder",
"arg1": "{{step.article_extract.output}}",
"max_total_chars": 15000,
"max_per_host": 2,
"max_sources": 8,
"chunk_size": 1000,
"output_key": "output",
"next": "context_gate"
},
{
"id": "context_gate",
"type": "condition",
"left": "{{step.evidence_rag.output}}",
"operator": "not_empty",
"right": "",
"on_true": "evidence_writer",
"on_false": "no_evidence",
"output_key": "output"
},
{
"id": "evidence_writer",
"type": "llm",
"provider_id": 2,
"ai_capability": "text",
"model": "",
"prompt_role": "user",
"prompt_text": "Keyword: {{keyword}}\nDomain: {{domain}}\n\nEvidence context:\n{{step.evidence_rag.output}}\n\n{{domain_aiprompt}}\n\nUse only supported claims from the evidence context. Respect KEY_CLAIMS and VERDICT credibility signals. Add a final section named Sources used. Return exactly:\n<TITLE>Title</TITLE>\n<DESCRIPTION>Meta description</DESCRIPTION>\n<CONTENT>Markdown article</CONTENT>",
"temperature": 0.35,
"max_tokens": 6144,
"output_key": "output",
"next": "evidence_content"
},
{
"id": "evidence_content",
"type": "regexp",
"mode": "extract_one",
"input_source": "{{step.evidence_writer.output}}",
"pattern": "<CONTENT>\\s*(.*?)\\s*</CONTENT>",
"flags": "si",
"output_key": "output",
"next": "evidence_quality"
},
{
"id": "evidence_quality",
"type": "ai_check",
"detector": "service_1",
"input_source": "{{step.evidence_content.output}}",
"threshold_ai_percent": 30,
"comparison": "less_than",
"on_pass": "evidence_title",
"on_fail": "quality_rejected",
"on_error": "quality_service_error",
"output_key": "output"
},
{
"id": "evidence_title",
"type": "regexp",
"mode": "extract_one",
"input_source": "{{step.evidence_writer.output}}",
"pattern": "<TITLE>\\s*(.*?)\\s*</TITLE>",
"flags": "si",
"output_key": "output",
"next": "evidence_description"
},
{
"id": "evidence_description",
"type": "regexp",
"mode": "extract_one",
"input_source": "{{step.evidence_writer.output}}",
"pattern": "<DESCRIPTION>\\s*(.*?)\\s*</DESCRIPTION>",
"flags": "si",
"output_key": "output",
"next": "evidence_article"
},
{
"id": "no_evidence",
"type": "stop",
"reason": "No usable evidence context was produced.",
"output_key": "output"
},
{
"id": "quality_rejected",
"type": "stop",
"reason": "Generated article failed the AI threshold: {{step.evidence_quality.output.ai_percent}}%",
"output_key": "output"
},
{
"id": "quality_service_error",
"type": "stop",
"reason": "AI text-check service failed: {{step.evidence_quality.output.error}}",
"output_key": "output"
},
{
"id": "evidence_article",
"type": "article_form",
"title": "{{step.evidence_title.output}}",
"description": "{{step.evidence_description.output}}",
"markdown": "{{step.evidence_content.output}}",
"lang": "{{domain_lang}}",
"slug": "",
"main_image": "",
"xfields_json": "{\"ai_percent\":\"{{step.evidence_quality.output.ai_percent}}\"}",
"public": 1,
"output_key": "article_data",
"next": "end"
}
] 🛠️ Troubleshooting
- Site does not accept publishing — check API URL, token in
classes/integration.php, rewrite rules, andapi.phpavailability. - auth_failed — the panel token does not match the site token.
- Model is missing in an LLM step — open the provider, fetch models with 🔄, or enter models manually.
- Template needs attention — an AI step has no provider, uses a deleted provider, or a macro points to a future/deleted step.
- Search returned 0 results — check the query, search backend, SERP limit, proxy/source availability.
- Article by URL did not load — make sure the previous step returns real URLs, not plain text without links.
- RAG is empty — make sure the article-fetching step returns documents with HTML content.
- Batch is stuck or timed out — increase batch wait time or use normal text mode.
- Publication succeeded but URL looks wrong — check Language / URL section, slug, title, and client-side slug generation.