Generate — Grounded Document Generation¶
docbrain generate produces a documentation draft grounded in your org's own
knowledge — your corpus, your past episodes, and your live MCP connectors —
and returns the markdown to you. It does not publish anything; it is
stateless. The moat is grounding: where a frontier model writes fluent generic
prose, generate writes what is actually true for your org (with per-claim
provenance), passes every DocBrain quality and safety gate, and is honest when
it doesn't know rather than fabricating.
When to use it¶
generate is the on-demand, you-drive-it counterpart to autopilot.
- Autopilot is automatic and gap-driven: DocBrain detects a documentation gap and proposes a draft on its own schedule. You don't ask for a specific document — the system decides what's missing.
- Generate is on-demand: you name the document you want, you hand it the primary source material, and you get the markdown back to do with as you please. It runs exactly when you invoke it and produces exactly what you asked for.
And versus a plain LLM:
| Plain frontier model | docbrain generate |
|
|---|---|---|
| Knows your runbooks, incidents, tickets, PRs, Slack threads | No | Yes — grounded in your corpus + episodes + live connectors |
| Per-claim provenance | No | Yes — every claim attributed to a source |
| Passes redaction / PII / hostname / injection gates | No | Yes — same gates as every DocBrain doc |
| Behaviour when it doesn't know | Fabricates fluent prose | Emits needs_input and says it can't |
Note
A frontier model with no access to your systems writes something that
reads right. generate writes something that is right for you — or
tells you which questions it can't answer.
CLI¶
<ASK> is the single required positional argument: a plain-language description
of the document you want written.
Flags¶
| Flag | Value | Meaning |
|---|---|---|
<ASK> |
string (positional) | Required. What to write. |
--source |
<FILE> |
Repeatable. A local file read verbatim as primary material. |
--source-url |
<URL> |
Repeatable. A link as primary material; DocBrain fetches it via the connected MCP connector. See Source material. |
--stdin |
— | Read primary material from stdin. |
--target |
<REF\|URL> |
Existing doc to augment (not replace). See Augmenting an existing doc. |
--template |
<FILE> |
Team template file that shapes sections and tone. Cannot affect safety. See Templates. |
--type |
<TYPE> |
Doc-type hint: runbook | guide | troubleshooting | faq | reference. Inferred if omitted. |
--space |
<SPACE> |
Confluence space whose quality rules apply. Falls back to the global floor if omitted. |
--out |
<FILE> |
Write markdown to a file instead of stdout. |
--no-enrich |
— | Disable live-MCP tool enrichment (corpus/seed only). |
--allow-violations |
— | Exit 0 even on error-severity quality violations (CI override). |
I/O contract¶
- stdout carries only the markdown. It is pipe-clean and redirect-safe — you
can
> out.mdor pipe into another tool without stripping noise. - All diagnostics go to stderr: resolved doc type, quality score, needs-input questions, skipped sources, and quality violations.
- Exit code is non-zero on error-severity quality violations — unless you
pass
--allow-violations. This is the CI-native behaviour: a bad draft fails the build by default.
# Runbook from local notes, redirected to a file (pipe-clean stdout)
docbrain generate "runbook for cert rotation" --source notes.md > out.md
Source material¶
You provide primary material in one of three ways. All three are repeatable and can be combined.
| Kind | Flag | What it is |
|---|---|---|
| File | --source <FILE> |
A local file, read verbatim. |
| Stdin | --stdin |
Material piped in on standard input. |
| URL | --source-url <URL> |
A link DocBrain fetches for you via the connected MCP connector. |
--source-url: supported links¶
A --source-url is an explicitly named primary source. DocBrain resolves it
through the connected MCP connector and reads the content for you:
- Confluence page —
…/wiki/…/pages/<id>or…/wiki/spaces/… - Jira issue —
…/browse/KEY-123 - Slack thread —
…/archives/C…/p… - GitHub PR —
…/pull/<n> - GitHub file —
…/blob/<ref>/<path>
# Postmortem grounded in a real Slack incident thread
docbrain generate "postmortem from this incident" \
--source-url https://acme.slack.com/archives/C123/p1700000000123 > postmortem.md
# API reference grounded in a GitHub PR's changes
docbrain generate "API reference for the changed endpoints" \
--source-url https://github.com/acme/repo/pull/42 --type reference
All-or-nothing hard-fail contract¶
Because a --source-url is a source you explicitly named, it is treated as
load-bearing. If any one of them can't be fetched, the whole generation
aborts — naming the failed source. It will never silently produce a document
from a subset of the sources you requested.
| Failure | Code |
|---|---|
| Connector not connected / not configured | 502 |
| Fetch error | 502 |
| Unsupported or unrecognized link | 400 |
Multiple --source-url flags are allowed, and all must succeed.
Named sources vs. auto-enrichment
The hard-fail contract applies only to sources you named with
--source-url. Auto-enrichment — the live-MCP context DocBrain pulls on its
own — is best-effort: a connector being down just means less context,
not a failed run. Named sources fail loud; enrichment degrades quietly.
URL-fetched content is size-bounded exactly like inline sources (per-source
and aggregate byte caps), so a huge linked file or thread can't overflow the
prompt. Over-budget material returns 413.
Templates¶
A template is a plain markdown/text file that shapes a document's structure and tone — nothing else. It can never carry or disable a safety or quality rule: the parser has literally no field for one.
Format¶
- An optional leading
doc_type: <type>metadata line. - An optional leading
tone: <free text>metadata line. - Every level-2-or-deeper markdown heading (
## Section) becomes a required section, in document order. Duplicates are de-duped; order is preserved. - All other text is ignored by the engine — prose, bullets, and blank lines
are guidance for the human writing the template, not consumed by generation.
An unknown metadata line such as
audience: SREsis simply ignored, not an error. - An empty or section-less file is fine (no structure constraint) — not an error.
- The only error case: a line whose key is a rule-like directive — one of
rules,rule,safety,secret(s),redaction/redact,quality_rule(s),policy/policies,allow,deny,disable,override(s)(e.g.safety: off,disable: redaction). The engine rejects it with400. You cannot smuggle safety config through a structure-only template; everything outside that small denylist is ignored.
Example template file¶
Seeded doc-type section sets¶
Each doc type ships with a built-in section set. A --template file
overrides/extends these for the run.
| Doc type | Required sections |
|---|---|
runbook |
Overview, Prerequisites, Steps, Verification, Rollback |
guide |
Overview, Prerequisites, Steps (optional: FAQ, Related Guides, Next Steps) |
troubleshooting |
Symptoms, Diagnosis, Resolution |
faq |
Free-form (no required sections) |
reference |
Overview, Parameters, Examples |
Usage¶
docbrain generate "runbook for cert rotation" \
--source notes.md \
--template templates/acme-runbook.md > out.md
Augmenting an existing doc¶
--target <REF|URL> points at an existing document and tells generate to
augment it — fill gaps, add sections, fold in new source material — rather
than rewrite it from scratch. The existing content is the baseline; the run adds
to it.
# Extend an existing runbook with what a new PR changed
docbrain generate "add the new rollback path" \
--target https://acme.atlassian.net/wiki/spaces/OPS/pages/12345 \
--source-url https://github.com/acme/repo/pull/42
API¶
Editor role; same auth as /ask. Returns 200 with a GeneratedArtifact.
Request body — GenerateRequest¶
{
"ask": "string (required)",
"sources": [
{ "kind": "file", "label": "notes.md", "raw": "<file bytes>" },
{ "kind": "url", "label": "https://github.com/acme/repo/pull/42", "raw": "" }
],
"target": "optional existing-doc reference to augment",
"template": "optional RAW template file CONTENT (the bytes, not a path)",
"doc_type": "optional hint",
"space": "optional confluence space",
"no_enrich": false
}
- For
kind: "url", the URL goes inlabelandrawis""— the server fetches it. templateis the raw template file content (the bytes), not a path.
Response — GeneratedArtifact¶
{
"markdown": "the generated document",
"doc_type": "resolved type",
"provenance": [
{ "section": "Overview", "source_ids": ["chunk-1", "chunk-2"] }
],
"needs_input": [ "questions the doc can't answer from available knowledge" ],
"skipped_sources": [
{ "label": "jira", "reason": "connector not connected" }
],
"quality": {
"score": 87.0,
"violations": [
{ "rule_name": "...", "severity": "warning", "message": "..." }
]
}
}
| Field | Meaning |
|---|---|
markdown |
The generated document. |
doc_type |
The resolved doc type (from --type/doc_type or inferred). |
provenance |
Per-section source attribution. Each entry is { section, source_ids } — section is the heading (null = whole-doc), source_ids the evidence chunks it was built from. |
needs_input |
Questions the doc can't answer from available knowledge — the honesty signal. |
skipped_sources |
Sources that were unavailable, each as { label, reason } (e.g. { "label": "jira", "reason": "tool not connected" }). |
quality |
{ score, violations } — score is a 0–100 number; each violation is { rule_name, severity, message }. |
Error codes¶
| Code | Meaning |
|---|---|
400 |
Bad request / unknown kind / unsupported URL (incl. a smuggled rule directive in a template). |
403 |
Caller is not an editor. |
413 |
Source material is over the size budget. |
502 |
A named URL source could not be fetched (connector not connected, not configured, or fetch error). |
503 |
Generation is not configured. |
Using generate in CI¶
docbrain generate is the one doc command worth putting in a pipeline, because
it is the only one that writes documentation grounded in your org's real
knowledge — your runbooks, past incidents, tickets, PRs, and Slack threads —
with per-claim provenance, and that is honest about what it doesn't know (it
emits needs_input instead of fabricating). In CI that turns into a concrete
property: documentation updates ship as a side effect of merging code,
grounded and quality-gated, with zero extra human work.
The three playbooks below cover the common shapes. They use acme as a
placeholder org — swap in your own connector URLs and spaces.
Playbook 1 — Doc-on-merge: update the runbook from the PR¶
The headline case. When a PR lands, regenerate the affected runbook from the PR itself plus the existing doc, and hand an engineer a review-ready draft.
# .github/workflows/docs-on-merge.yml
name: Update runbook on merge
on:
pull_request:
types: [closed]
paths: ['services/payments/**']
jobs:
update-runbook:
if: github.event.pull_request.merged == true
runs-on: ubuntu-latest
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
DOCBRAIN_TOKEN: ${{ secrets.DOCBRAIN_TOKEN }}
steps:
- name: Generate runbook draft grounded in the PR
run: |
docbrain generate "update the deploy runbook for the changes in this PR" \
--source-url https://github.com/acme/payments/pull/${PR_NUMBER} \
--target https://acme.atlassian.net/wiki/spaces/SRE/pages/12345/Deploy+Runbook \
--type runbook --allow-violations --out runbook-draft.md
What this actually does:
- Fetches the PR through the connected GitHub connector. The
--source-urllink is resolved server-side via the MCP connector you already configured — the runner needs no GitHub credentials of its own for the fetch, just a DocBrain token. - Grounds the draft in both the PR and your org corpus. The model sees the
PR diff and the existing runbook (via
--target) and whatever auto-enrichment pulls from related incidents, tickets, and threads. The output is what is true for acme's deploy, not generic prose. - Augments — it does not overwrite.
--targettreats the existing page as the doc being revised, so structure and prior content are preserved and the draft is a coherent next version, not a from-scratch rewrite. - Every claim carries provenance. The draft and the API
provenance[]array trace assertions back to the sources they came from, so a reviewer can verify rather than trust.
The runbook-draft.md artifact is the input to a human review step — for
example, post it as a PR comment or open a docs PR:
This playbook uses --allow-violations deliberately: a merge-time draft is
advisory and a human gates it, so you want the draft even if it trips a quality
rule. Playbook 3 is where you flip that.
Playbook 2 — Diff-driven: generate from a git diff file¶
Not every pipeline has a PR URL to pass — pre-merge jobs, GitLab/Jenkins/
Buildkite runs, and local pre-commit checks often only have the working tree. In
that case, hand generate the diff on disk with --source:
- name: Generate API notes from the diff
run: |
git diff origin/main...HEAD > changes.diff
docbrain generate "document the behavior change in these endpoints" \
--source changes.diff --type reference --out api-notes.md
When to use which form:
Use --source-url <PR> (Playbook 1) |
Use --source <diff> (Playbook 2) |
|---|---|
| You have a merged/open PR URL | You only have the working tree |
| GitHub, with the connector configured | Any CI system — no connector needed for the diff |
| Post-merge "doc the shipped change" | Pre-merge "doc the proposed change" |
| Connector also pulls PR comments/reviews | Diff is exactly what's on disk, nothing more |
--source is a plain local file read by the runner, so it works anywhere git
runs and needs no connectivity to your wiki for the diff itself. Auto-enrichment
still reaches into your org corpus for grounding context unless you pass
--no-enrich.
Playbook 3 — Quality gate: fail the build on a bad doc¶
Drop --allow-violations and generate becomes a gate. The CLI exits
non-zero on error-severity quality violations, so the doc must clear the bar
or the build breaks.
- name: Generate and gate the troubleshooting doc
run: |
docbrain generate "document the new retry/backoff behavior" \
--source changes.diff --type troubleshooting \
--space SRE --out incident-doc.md
If the draft fails a quality rule at error severity, the step's exit code is non-zero and the job fails — no human in the loop, the pipeline simply won't pass a sub-bar doc. The stdout/stderr contract is what makes this safe to script:
- stdout is only the markdown. Pipe-clean and redirect-safe —
> incident-doc.mdcaptures the document and nothing else. No banners, no progress logs. - stderr carries every diagnostic. Doc type, quality score, the
needs_inputlist,skipped_sources, and the specific violations all land on stderr where your CI logs collect them without polluting the artifact. - The exit code is the CI signal. A zero exit means the doc met the bar, non-zero means it didn't — you don't parse logs to decide pass/fail.
--space <SPACE> applies that Confluence space's quality rules to the run, so
the doc is held to the same standard your space enforces for human-authored
pages — in CI, before anyone sees it.
Why this beats a generic LLM action¶
A frontier model dropped into a generic GitHub Action writes plausible prose.
docbrain generate writes what is true for your org — or says it can't:
- Grounded in org reality, with provenance. It reads your actual PRs,
runbooks, incidents, tickets, and threads through your connectors, and every
claim traces back to a source in
provenance[]. A generic action has no access to any of that and invents the specifics. - Honest
needs_inputinstead of fabrication. When the corpus doesn't cover something,generateflags it inneeds_input[]rather than confidently making it up — you fix gaps on purpose instead of discovering an invented procedure during an incident. - The same safety gates as every other DocBrain doc, enforced in CI. Secret and PII redaction, hostname scrubbing, and prompt-injection quarantine run on the generated draft exactly as for human-authored content — your pipeline can't accidentally publish a leaked credential or a poisoned source.
- Quality-gated by exit code. Without
--allow-violations, a sub-bar doc fails the build. A generic LLM action always exits zero and ships whatever it produced; DocBrain makes "good enough to publish" a machine-checkable condition.
Safety¶
A generated document passes the same gates as every other DocBrain doc —
there is no weaker path through generate:
- Secret / PII redaction — credentials and personal data are stripped.
- Hostname scrub — internal hostnames are removed.
- Injection quarantine — prompt-injection content in fetched/linked sources is quarantined, not executed.
- Structural + style scoring — the
quality.scoreandviolationsthat drive the CI exit code.
Templates cannot weaken safety. A template shapes structure and tone only;
the parser has no field for a safety or quality rule, and any rule-like
directive is rejected with 400. There is no template knob that turns off
redaction, the hostname scrub, or the injection quarantine.
needs_input honesty. When the document can't answer something from the
available knowledge, generate says so — it emits the open questions in
needs_input rather than fabricating a confident-sounding answer. Honest gaps
over fluent fiction.