DEV Community

Avery Lin
Avery Lin

Posted on

Draft with a Model, Sign with a Human: A Documentation Ownership Workflow

Documentation quality is decided by the ownership boundary before the prompt is written, not by the model's fluency. A model can draft descriptions, signatures, and happy-path examples, but it cannot know which invariants your team is willing to defend. This article defines a workflow where the model produces a first draft and a human explicitly signs the claims that matter.

The failure mode

Most documentation pipelines treat generation as a text problem: feed in code, get prose, publish. The result is a page that reads well and contains no obvious lies, yet still fails because it omits the one constraint that changes behavior. A common failure appears in API references where the generated text describes parameters but never states that a null value disables retries. That sentence is not a style improvement; it is a correctness requirement.

The ownership matrix

Before generating anything, write down what the model may own and what a human must own. The table below is a starting point that works for libraries, CLIs, and internal services.

Artifact Model may draft Human must own
Function signatures and parameter names Extracted from the AST The semantic meaning of null, empty, and default values
Usage examples Derived from existing tests Which examples are supported and which are accidental
Invariants and failure modes Proposed candidates Confirmation that each invariant is true and testable
Deprecation and deletion criteria A suggested timeline The actual removal policy and migration plan
Decisions and rationale A summary of code comments The reasoning behind non-obvious choices

The workflow

Use this workflow when you need to ship documentation for a module that already has tests and a stable public surface. It takes about an hour for a typical package and produces a file that CI can check.

1. Define the contract

Create a markdown template with explicit ownership markers. Every section gets either <!-- OWNER: model --> or <!-- OWNER: human -->; the second type requires a Reviewed-by line before merge. This marker is the contract, and the prompt is just the instruction that fills it.

# docs/template.md
<!-- OWNER: model -->
## Overview

<!-- OWNER: model -->
## API reference

<!-- OWNER: human -->
## Invariants
<!-- Reviewed-by: -->

<!-- OWNER: human -->
## Deprecation policy
<!-- Reviewed-by: -->
Enter fullscreen mode Exit fullscreen mode

2. Generate the draft

Feed the template and the module's public symbols into a generation pass. In this workflow, MonkeyCode's free model access and free server option provide a low-cost way to run that pass in a disposable environment; the same steps work with any provider. Disclosure: This article was prepared as part of MonkeyCode's product outreach. Keep the prompt narrow: ask for descriptions, not decisions.

# generate_docs.py
from pathlib import Path
import subprocess

def public_symbols(path: Path) -> str:
    # Simplified: grep for top-level definitions.
    return subprocess.run(
        ['grep', '-nE', '^(def|class) ', str(path)],
        capture_output=True, text=True
    ).stdout

def build_prompt(template: Path, source: Path) -> str:
    return f'''
{template.read_text()}

Generate documentation for the public symbols below.
Do not invent invariants. Mark every unverified claim as UNVERIFIED.

{public_symbols(source)}
'''
Enter fullscreen mode Exit fullscreen mode

3. Lint the ownership

Run a small script that fails when a human-owned section has no reviewer. This lint does not check truth; it checks that someone has claimed responsibility for the truth.

# doc_ownership.py
import re
import sys
from pathlib import Path

HUMAN_OWNED = re.compile(r'<!--\s*OWNER:\s*human\s*-->', re.I)
REVIEWED = re.compile(r'<!--\s*Reviewed-by:\s*(.+?)\s*-->', re.I)

def check(path: Path) -> list[str]:
    text = path.read_text()
    errors = []
    for i, line in enumerate(text.splitlines(), 1):
        if HUMAN_OWNED.search(line) and not REVIEWED.search(line):
            errors.append(f'{path}:{i}: human-owned section lacks Reviewed-by')
    return errors

if __name__ == '__main__':
    problems = [e for p in sys.argv[1:] for e in check(Path(p))]
    if problems:
        print('\n'.join(problems))
        raise SystemExit(1)
    print('ownership contract satisfied')
Enter fullscreen mode Exit fullscreen mode

4. Review the human-owned sections

Read only the sections marked human-owned, not the whole page. For every invariant, ask two questions: is this true today, and will CI catch it if it changes? If the second answer is no, delete the sentence or add a test that protects it.

5. Measure the handoff

Track two numbers: time from draft to merge and the number of corrections made to human-owned sections. If corrections stay high, the contract is wrong, not the model. Adjust the matrix until the model drafts only what your reviewers can verify quickly.

Limitations and who should not use this

A model cannot know the hidden constraints that live in issue comments, incident reviews, or the memory of the person who wrote the code. This workflow also assumes that a human reviewer exists; a team without a designated doc owner will skip the signing step and recreate the original problem. If your documentation must satisfy regulatory traceability, use a formal review system instead of markdown comments. The free model access and free server option are useful for experiments, but they are not a substitute for the ownership boundary.

Try this on one module's README and compare the review time against your previous process. The boundary, not the model, will tell you where the real cost lives.

Top comments (0)