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.

Start with a ready-to-use pipeline. A template can handle research, RAG, writing, review, Article Form mapping and WordPress/CMS publishing. After that you can change every step for your own sites, sources and editorial rules.
Where to publish? Two ways:
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.
  1. 🌐 Domains — connect a site, API URL, API token, and domain-specific AI prompt.
  2. 🎨 External site template — after installing the client package, customize .cms/temp: header, menu, cards, sidebar, styles, metrics, and footer.
  3. 🤖 AI Providers — add OpenAI, Gemini, Anthropic, xAI, DeepSeek, Qwen, or a Custom OpenAI-compatible API.
  4. 📝 Prompts — save reusable instructions: SEO rules, style, constraints, and article format.
  5. 🔍 Keywords — create keyword sets for generation.
  6. ⚙️ Templates — build a pipeline from search, parsing, RAG, AI generation, processing, and the final article form.
  7. ▶️ Tasks — connect a domain, template, and keyword sets, then choose the publishing mode and start the job.
  8. 📝 Articles — review, create, or manually edit articles when needed.
The safest order is: site, token, and the public .cms/temp template first, AI provider second, prompts and keywords third, pipeline template fourth, pipeline test fifth, production task last.
You can upload the .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

  1. Open the 🌐 Domains tab.
  2. Click Download client (.zip).
  3. Extract the archive. It contains the site package files: index.php, classes/, pages/, temp/, vendor/, and other service files.
Download the current client archive with the Download client (.zip) button in Domains.

Option 1: install into a separate blog folder

  1. Create a blog folder in the target site's root directory.
  2. Copy the extracted client package into that folder.
  3. Check that the API is available at https://example.com/blog/api.php.
  4. If the host returns 404, configure rewrite rules so requests inside /blog/ are handled by the client index.php.
  5. 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

  1. Copy the extracted client package directly into the site root.
  2. 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.
  3. In the panel domain settings, set API URL to https://example.com/api.php.

API Token

  1. On the site, open classes/integration.php.
  2. In cms_get_api_token(), replace the default value with a unique long string.
  3. In the panel, open 🌐 Domains → Add domain or edit an existing domain.
  4. Paste the same string into API Token.
function cms_get_api_token(): string
{
    return 'your-unique-secret-token-here';
}
The panel sends publications with 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.php or https://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.

After adding a provider, always fetch or manually enter models. If a model is not saved on the provider, it cannot be selected correctly in a template.

📝 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 EN or CBD 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.
  • TypeNew articles, Article updates, or Keywords. 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.
Important: card position on the canvas and the execution route are different. In graph mode, routing is controlled by 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_form maps 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_form prepares a replacement version for that exact article.
  • Keywords — input is one or more seed phrases. The final keywords_form normalizes the generated lines and adds them to a selected keyword set.
The type is a contract, not a label. A New articles template cannot be used to update existing posts, and a Keywords template cannot publish an article.

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.
  • Roleuser or system.
  • 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.

  • 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.

Repeated execution overwrites the normal output of that step. To keep all FOR EACH results, accumulate an array in a separate JavaScript step, as shown in Example - FOR EACH Sections.

🧩 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.
Slug and language are usually created at publishing time. In a task, you can force Language / URL section, for example 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}}.

If a step starts referencing a future or deleted step after reordering, the template gets a Needs attention badge. Fix the macro or execution order before running a task.

🧭 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.
  • stop or end as 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.

Before running a template, select an active provider and a suitable model in every AI step.

🧠 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.

Reasoning support is model-specific. A provider connection may work with one model but reject a setting on another, so select the exact model and run a short template test first.

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 cachedContents is disabled in the panel. For Gemini, AIPANEL_CACHE markers 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}}
Important: do not treat 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.

A test run checks more than text quality. It verifies macro substitution, search results, URL loading, token limits, and final form mapping. It does not publish material, change an article, or add keywords to a set.

▶️ 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.
For Article updates, review is the default. The Awaiting review state means the proposed version is ready but has not been applied to the site.

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.

The panel's ⚙️ Templates tab controls content generation. The site's .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

  1. Open .cms/temp/temp.php.
  2. Replace the site name, for example $siteName, $defaultTitle, and $siteTagline.
  3. Review navigation links: Home, Blog, About, Contact, Privacy, Terms. Remove what is unnecessary or add the required sections.
  4. Review the hero text on the home/listing page. It must match the site's niche, language, and audience.
  5. Review the footer: project name, description, contacts, and legal links.
  6. Open .cms/temp/css/style.css and customize the palette in the :root block: background, brand color, accent color, container width, radius, and spacing.
  7. Open .cms/temp/metric.php and 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.
  8. Open files in .cms/pages/ and replace the default texts with pages for the specific project.

Configuring the Telegram contact form

Important: the form on the 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.
  1. Create your own bot through @BotFather and obtain its bot token.
  2. Send any message to the bot, then obtain the target private chat or group ID through Telegram's getUpdates method.
  3. Open .cms/classes/send.php.
  4. Enter the token in $telegram_api_token and the chat ID in $chat_id.
  5. Open the site's contact page 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';
Do not place the token in 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 inside short.php or sidebar.php.
  • $tophost, $hostname, $incpage, $currentLang, $pagePath — service variables for URL, domain, language, and current page.
When editing 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

  1. For the home page and pagination pages, the engine collects published articles, renders each item through short.php, then outputs the result through temp.php.
  2. For an article page, the engine loads the article, prepares $row['html_content'], builds $row['sidebar'] through sidebar.php, then includes temp.php.
  3. If the template folder contains fullpage.php, it can be used for specific internal pages instead of the standard temp.php.
  4. metric.php is usually included from temp.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.php has no foreign ad network or analytics IDs.
  • .cms/classes/send.php contains 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, and about pages 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

  1. Download the WordPress bridge script: in the documentation header or domain settings, click Download WordPress Client (auth required).
  2. Copy the downloaded api.php file to the root directory of your WordPress installation (where wp-load.php is located).
  3. Open your WordPress wp-config.php file and define a secure API token:
    define('AIPANEL_API_TOKEN', 'your-long-secure-token');
  4. 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

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_json field (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 post type. You can override this by passing the post type inside xfields_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.

TemplateWhat it demonstratesCore route
Example - Graph IF ELSE and MergeIF/ELSE, two AI branches, Merge, JavaScript, RegExp, and article form.condition → writer → merge → script → article_form
Example - AI Check Rewrite LoopStrict AI threshold, three check routes, a bounded WHILE loop, and STOP.draft → check → loop/rewrite → check → article
Example - FOR EACH SectionsArray preparation, loop variables, per-item processing, and result accumulation.script → for_each → AI → accumulator → loop → article
Example - Source RAG Quality GateSearch, 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, and api.php availability.
  • 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.
Start diagnostics from Task details and step debug. They show where the pipeline stopped, what input the step received, and what output it returned.