Shipping Better Docs with AI: Restructuring Module Parameters for Clarity and Consistency
As you may know from past articles, we’re in the process of a major documentation overhaul. I wanted to share a focused task we’ve been working on over the past couple of days. This effort highlights our practical, responsible approach to an “AI-First” strategy, where AI is a powerful tool guided by human expertise, not a replacement for it.

The main goal is to give every rsyslog module parameter its own dedicated page with stable, linkable anchors, clear usage examples, and legacy mappings. This is a critical step towards making our documentation not only more accessible for humans but also better structured for consumption by AI assistants. The ultimate aim is to make our docs the best possible source of truth, no matter how a user chooses to access the information.
TL;DR
We’re reorganizing rsyslog’s module docs so every parameter has its own page with stable anchors, summaries, usage examples, and legacy mappings. A three-stage, AI-assisted workflow helps us do it safely and at scale:
- A human designs a “root prompt” to codify all the editorial rules.
- A separate agent generates the changes in a git patch.
- An independent AI (Gemini) and our CI pipeline validate the patch for accuracy.
This process is careful, transparent, and iterative, with humans in control at every step. Try the rsyslog Assistant at https://rsyslog.ai to see some of the results and help us improve.
The Challenge: Making Documentation More Accessible
Historically, our module documentation has grouped all parameters onto a single page. This “everything in one place” approach made it difficult to link directly to a specific parameter and created maintenance headaches. Our new approach tackles these issues head-on by adopting a parameter-per-page model. This means:
- Each parameter gets its own file, for example,
doc/source/reference/parameters/<module>-<parameter>.rst
. - The main module page acts as a central hub, pulling in a concise summary for each parameter into a neatly organized list.
- Every parameter page is a self-contained, structured unit, complete with stable anchors, a summary block, metadata, clear usage snippets, and legacy directive mappings.
This restructuring directly benefits anyone using rsyslog by making our documentation more precise and easier to navigate. It also simplifies maintenance for contributors, as changes are isolated and predictable, and it lays the groundwork for more effective automated checks. We’re applying this to over 50 modules while ensuring there is zero content loss.
The Workflow: Three AIs, One Human-in-the-Loop Process
To handle this massive task responsibly, we’ve developed a multi-stage, human-driven workflow. The goal is to leverage the speed of automation while maintaining control and ensuring accuracy.
- Root Prompt Design (Human + ChatGPT): This is where our human expertise comes in. We first codify our detailed editorial and structural rules into a root prompt. This prompt is a comprehensive set of instructions that a large language model follows. We iteratively refine this prompt by running it on a single module, checking the output, and adjusting the instructions as needed until we’re confident in the rules.
- Codex Agent Execution: A specialized agent takes the final, refined root prompt and applies it to an entire module’s documentation. It then generates a git patch containing the newly created parameter files and the updated module page. If the patch misses something, we loop back to Step 1 and adjust the root prompt, not the output, to fix the underlying issue.
- Independent Review (Gemini) and CI Validation: The generated patch is then submitted for review. First, we use a separate AI (Gemini) to critique the patch for consistency and potential edge cases. This independent check prevents one model from grading its own work. If the patch fails this review, we again go back to Step 1 to refine the prompt. Finally, our automated Sphinx build and other CI checks act as the ultimate, deterministic gatekeeper before the patch can be merged. Any failure here also sends us back to Step 1 to fix the root prompt.
Humans are in control at every stage, from designing the rules to reviewing the final output. The AI acts as a powerful assistant, not a decision-maker.
Responsible AI in Open Source
We understand that the use of AI in open source projects can be met with skepticism. That’s why we’re committed to a transparent and responsible approach:
- Transparency: The root prompt itself is open for anyone to review, critique, or improve. This document defines the entire process and ensures there are no hidden rules.
- Reproducibility: Given the same root prompt and the same input, our process is designed to produce a consistent and predictable output.
- Human Ownership: Our maintainers and contributors remain the final arbiters. The AI is a tool to assist in a large-scale editing task; it does not make the final decisions.
The ultimate goal is to produce better documentation, not to use AI for its own sake.
How You Can Help
Your feedback is invaluable. This effort will ultimately improve both the documentation and the rsyslog assistant, as the docs serve as a primary training source.
- Use the rsyslog Assistant: Visit https://rsyslog.ai, ask it questions about parameters or how-to’s, and let us know where the answer is wrong or unclear.
- File Issues on the Docs: If you find a broken link, a missing legacy note, or an inconsistent casing, please report it. We can then fix the issue in the root prompt and regenerate the documentation for consistency across all modules.
- Contribute a Pull Request: If you like our new documentation structure and have the time, you’re welcome to help us migrate another module by following the new pattern.
We expect to make steady progress on this effort over the coming weeks. As always, your contributions and feedback are what make rsyslog better.
Appendix: Current Root Prompt (excerpt we run for each module)
Rsyslog docs: parameter split root prompt — v4 (no‑content‑loss, scope‑correct anchors)
SET MODULE FOR THIS RUN
-----------------------
Set <module-name> to the module you are converting. For this run:
<module-name> = imfile
Change only this value for other modules in later runs.
Task
====
Restructure rsyslog docs for the given module (<module-name>) by splitting every logical parameter from
`doc/source/configuration/modules/<module-name>.rst` into **single canonical parameter pages** (one page per parameter name),
documenting all applicable scopes (module/action/input) on the same page. Apply strict labeling, H1 headings, include-slice summaries,
and `<module-name>-<param>` slug rules. Handle legacy names. Keep CI light (PoC).
Absolute no-content-loss policy
-------------------------------
- **Do NOT reduce information.** Carry over all cautions, platform caveats (e.g., `recvmmsg()` availability, kernel behavior), limits,
ranges, examples, notes about privilege requirements, performance trade-offs, warnings about crashes, and recommendations.
- If the original page has examples, **preserve them**, placing them under *Description* or the relevant *Usage* section (add a short "**Examples:**" list or literal blocks).
- Preserve any `.. versionadded::`, `.. warning::`, `.. note::`, and similar admonitions.
- If the original text contains strong advice (e.g., “do not set above 128”), **keep it verbatim or paraphrase with equal specificity**.
- If a parameter is platform-gated (e.g., only active on Linux, or ignored if feature not present), explicitly state that.
- If the earlier docs described a legacy type like “binary”, map it to **boolean** but mention that mapping in **Notes**.
Scope & constraints
-------------------
- Touch only:
- `doc/source/configuration/modules/<module-name>.rst`
- new files under `doc/source/reference/parameters/`
- Do not invent parameters; enumerate **exactly** those present on the module page.
- Preserve semantics; normalize wording/formatting lightly.
- US-ASCII only. Config snippets use `.. code-block:: rsyslog`.
- rsyslog params are case-insensitive; **display** preserves historical CamelCase and dots; filenames/labels use lowercase slugs.
- Transitional split: future automation will rely on these paths & labels.
Location, filename & slug
-------------------------
- **One page per parameter** at: `doc/source/reference/parameters/<module-name>-<param-slug>.rst`
- **Slug rule** for `<param-slug>`: lowercase; replace `.` with `-`; remove other punctuation; trim spaces.
Examples: `RateLimit.Interval` → `ratelimit-interval`; `compression.zstd.workers` → `compression-zstd-workers`.
Top-of-page order (labels → H1 → index → summary → backlink)
------------------------------------------------------------
```
.. _param-<module-name>-<param-slug>:
.. _<module-name>.parameter.<scope>.<param-slug>:
.. _<module-name>.parameter.<param-slug>: # optional scope-free alias (keep at most one)
<ParamName>
==========
.. index::
single: <module-name>; <ParamName>
single: <ParamName>
.. summary-start
<one concise sentence; ≤160 chars; use backticks for tokens>
.. summary-end
This parameter applies to :doc:`../../configuration/modules/<module-name>`.
```
Rules:
- The **second label MUST use the actual primary scope** of the parameter: `<scope>` ∈ {`module`, `action`, `input`}.
(Example: `.. _imudp.parameter.input.port:` for an input-scoped parameter.)
- The scope-free alias label is **optional**; include at most one: `..<module>.parameter.<param-slug>:`.
- The summary markers **must** be isolated as RST comments with a blank line before and after both `.. summary-start` and `.. summary-end`,
and there must be a blank line **after** the one-line summary. This prevents marker text from leaking into HTML.
Compact header field list (omit empty fields entirely; keep order)
------------------------------------------------------------------
```
:Name: <ParamName as in config (keep CamelCase and dots)>
:Scope: module | action | input # list all that apply
:Type: string (see :doc:`../../rainerscript/constant_strings`) | integer | boolean | size | word | uid | gid | array[string] | positive-integer
:Default: module=<val>; action=<val or "inherits module">; input=<val> # include only scopes that apply
:Required?: yes | no
:Introduced: <version> | at least 8.x, possibly earlier
```
Introduced fallback policy
--------------------------
- Default: `at least 8.x, possibly earlier`
- If any **legacy equivalent** is listed on the page: `at least 5.x, possibly earlier`
Sections (exact order; omit a section if empty)
-----------------------------------------------
```
Description
-----------
<Shared semantics across scopes. If multiple scopes apply, state precedence: action > module > hardcoded default. Carry all warnings, caveats, ranges, platform notes, examples.>
Module usage
------------
<Only if :Scope: contains module>
.. _param-<module-name>-module-<param-slug>:
.. _<module-name>.parameter.module.<param-slug>-usage:
.. code-block:: rsyslog
module(load="<module-name>" <ParamName>="...")
Action usage
------------
<Only if :Scope: contains action>
.. _param-<module-name>-action-<param-slug>:
.. _<module-name>.parameter.action.<param-slug>-usage:
.. code-block:: rsyslog
action(type="<module-name>" <ParamName>="...")
Input usage
-----------
<Only if :Scope: contains input>
.. _param-<module-name>-input-<param-slug>:
.. _<module-name>.parameter.input.<param-slug>-usage:
.. code-block:: rsyslog
input(type="<module-name>" <ParamName>="...")
Notes
-----
- Use backticks for API names/options (e.g., ``recvmmsg()``, ``List Containers``).
- Map legacy wording like “binary” to **boolean** here if applicable.
- Carry over any ``.. versionadded::`` info if present.
- If the OS may adjust a requested value (e.g., `SO_RCVBUF`), say so. Mention privilege needs if relevant.
- If the parameter is **experimental** or dangerous, say so explicitly (and keep any crash warnings).
Legacy names (for reference)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<Include ONLY if legacy tokens exist>
Historic names/directives for compatibility. Do not use in new configs.
.. _<module-name>.parameter.legacy.<legacy-slug-1>:
- <LegacyToken1> — maps to <ParamName> (status: legacy)
.. index::
single: <module-name>; <LegacyToken1>
single: <LegacyToken1>
See also
--------
See also :doc:`../../configuration/modules/<module-name>`.
```
Type normalization
------------------
- Allowed set: `string | integer | boolean | size | word | uid | gid | array[string] | positive-integer`.
- Treat legacy “binary” as **boolean**; note this in **Notes** when applicable.
- For `string`, always link semantics to `:doc:`../../rainerscript/constant_strings``.
- If a specialized token appears (e.g., `FileCreateMode`, `UID`, `GID`, `size`), keep it in `:Type:` and optionally clarify in **Notes**.
- Keep examples from original docs (e.g., single vs. array forms).
Module page updates in `doc/source/configuration/modules/<module-name>.rst`
---------------------------------------------------------------------------
- Keep original group sections (**Module Parameters**, **Action Parameters**, **Input Parameters**).
- Replace inline parameter docs with a **two-column list-table** (Parameter | Summary). The Parameter column must link to the **page-level** anchor (top of page), not a scope anchor.
```
.. list-table::
:widths: 30 70
:header-rows: 1
* - Parameter
- Summary
* - :ref:`param-<module-name>-<param-slug>`
- .. include:: ../../reference/parameters/<module-name>-<param-slug>.rst
:start-after: .. summary-start
:end-before: .. summary-end
# repeat per parameter in this group
```
- Add once per module page (replacing any older variant):
```
.. note::
Parameter names are case-insensitive; CamelCase is recommended for readability.
```
- Add a hidden toctree listing all parameter pages (prevents orphaned pages and aids navigation):
```
.. toctree::
:hidden:
../../reference/parameters/<module-name>-<param-slug-1>
../../reference/parameters/<module-name>-<param-slug-2>
# (all generated parameter pages)
```
Normalization & wording rules
-----------------------------
- Preserve CamelCase/dots in headings and examples; slugs/labels are lowercase with dots→hyphens.
- Backticks for function names, API endpoints, and option keys.
- Fix only **clear typos**. If you fix one, you may add a hidden alias label (rare; high confidence only).
- Omit **Legacy names** section if no legacy tokens.
- Elide other empty fields.
- When the original page includes concrete limits/recommendations (e.g., “do not set above 128”), **keep them**.
Light CI (PoC)
--------------
- Run (non-blocking):
- `sphinx-build -b html doc/source doc/_build/html`
- (Optional) `sphinx-build -b linkcheck doc/source doc/_build/linkcheck`
- Provide a brief summary of warnings/broken links if you run linkcheck; **do not** fail the task on warnings.
Deliverables
------------
1) Unified diffs for all created/modified files.
2) Mapping: original parameter names → `<param-slug>`, plus detected **Scope**(s).
3) Updated list-table block(s) for each group in `<module-name>.rst` and the hidden toctree block (paste-ready).
4) (Optional) Build summary if Sphinx was executed.
Execution notes
---------------
- Parse `<module-name>.rst` to enumerate parameters across all groups (module/action/input).
- Create **one canonical page per parameter** with multi-scope sections as applicable.
- Ensure module overview tables pull their Summary via the include slice to avoid divergence.
- Set `:Introduced:` using explicit version data if found; otherwise apply the fallback policy above.
- Preserve all details and warnings; do not shorten content.
- Begin now and return the diffs plus the mapping table.
Self-check before returning
---------------------------
- [ ] All page-level labels present and correct:
`param-<module>-<slug>` and `<module>.parameter.<scope>.<slug>` (scope matches parameter’s primary scope).
- [ ] All usage-section labels present and end with `-usage` for their scope:
`<module>.parameter.module.<slug>-usage`, `<module>.parameter.action.<slug>-usage`, `<module>.parameter.input.<slug>-usage`.
- [ ] Optional alias `<module>.parameter.<slug>` present at most once.
- [ ] Summary markers are isolated with required blank lines; one-line summary ≤160 chars, no trailing markers in HTML.
- [ ] US-ASCII only; backticks used for tokens, APIs, and function names.
- [ ] `:Type:` values are from the allowed set; `string` links to `constant_strings`; legacy “binary” mapped to boolean with note.
- [ ] All platform gating (e.g., `recvmmsg()`), privilege notes, ranges, caveats, and crash warnings are preserved.
- [ ] Examples from original docs are included where they existed.
- [ ] Module page shows list-table with include slices and a hidden toctree containing all generated parameter pages.
- [ ] No parameters invented, none omitted.
- [ ] Anchors are unique; no collisions across pages.