ennosv
Living Documentation

Living Documentation

How We Keep Our Docs Alive Without the Maintenance Burden

Aug 1, 2025 Engineering

When building software - maintaining separate documentation is a losing battle. Every feature we shipped made our docs slightly more out of date. Sound familiar? Here's how we solved it by making our code document itself.

What Living Documentation Really Means

Living documentation isn't about better writing or more discipline. It's about making documentation emerge naturally from the code you're already writing. When docs can't drift from reality because they are the reality, you've achieved living documentation.

Six Patterns We Actually Use (With Real Examples)

1. Schema-as-Documentation

The Pattern: Your type definitions become your API docs automatically. Write the schema once, get validation and documentation for free.

How to implement it simply:

// Define your schema (you need this anyway for validation)
const stepSchema = z.object({
	type: z.enum(['split', 'prompt', 'join']),
	params: z.optional(z.record(z.any()))
});

// Generate docs automatically
const jsonSchema = zodToJsonSchema(stepSchema);
const markdown = generateMarkdown(jsonSchema);

In Papyria: Our workflow schemas automatically generate the API documentation - when we add a new step type like "structured_prompt", the docs update themselves.

2. Executable Examples

The Pattern: Your example files actually run. No more outdated tutorials that don't work.

How to implement it simply:

# This file is both documentation AND configuration
name: Process customer feedback
steps:
  - type: split # Break into manageable chunks
  - type: sentiment # Analyze each chunk
  - type: summarize # Create summary

In Papyria: Our workflows/lbs.yaml isn't just an example - it's the actual configuration economists use to analyze government spending patterns.

3. Type-Driven Contracts

The Pattern: Let your type system document what works with what. If it compiles, it's documented.

How to implement it simply:

// The types ARE the documentation
type MarkdownInput = { data: string; mimeType: 'text/markdown' };
type HtmlOutput = { data: string; mimeType: 'text/html' };

function markdownToHtml(input: MarkdownInput): HtmlOutput {
	// Implementation
}

In Papyria: Our workflow validator checks if step outputs match the next step's inputs - this validation logic doubles as documentation of valid step combinations.

4. Architecture Decision Records

The Pattern: Document the "why" behind decisions as numbered, immutable records. Future you will thank present you.

How to implement it simply:

# 003. We chose YAML over JSON

Status: Accepted
Context: Users need to define workflows without coding
Decision: YAML with schema validation
Consequence: Easier for humans to write, still validates

In Papyria: Our ADRs explain why we moved from LangChain to direct LLM integration - context that would otherwise be lost.

5. Tests as Specifications

The Pattern: Your tests describe what the system does better than any prose could.

How to implement it simply:

describe('text splitter', () => {
	it('respects sentence boundaries', () => {
		expect(split('Hello. World.')).toEqual(['Hello.', 'World.']);
	});

	it('handles minimum chunk size', () => {
		expect(split('Hi', { minSize: 5 })).toEqual(['Hi']);
	});
});

In Papyria: Our splitter tests document exactly how text chunking behaves with different separators and overlap settings.

6. Self-Documenting Errors

The Pattern: When something fails, the error message teaches the user how to fix it.

How to implement it simply:

if (!isCompatible(outputSchema, inputSchema)) {
	throw new Error(`
    Cannot connect ${fromStep} to ${toStep}
    
    ${fromStep} outputs: ${prettyPrint(outputSchema)}
    ${toStep} expects: ${prettyPrint(inputSchema)}
    
    Try adding a conversion step between them.
  `);
}

In Papyria: When workflow steps don't connect, our error messages show exactly what schemas are incompatible and suggest fixes.

The Real Magic: It Just Happens

Here's what we love most about living documentation - it requires zero additional effort once set up. We're pragmatists at heart, so if documentation required extra work, we'd skip it. But when:

  • Adding a feature automatically updates the docs
  • Writing a test creates an example
  • Defining a type documents an interface
  • Fixing a bug improves error messages

...then documentation stops being a chore and becomes a natural byproduct of writing good code.

Our Honest Take

Living documentation isn't perfect. Sometimes you still need a good README or a getting-started guide. But for API docs, examples, and technical specifications? We've eliminated 90% of the maintenance burden.

The perfectionist in us loves that the docs are always accurate. The pragmatist loves that we don't have to maintain them. And our users? They just love that when they read our docs, they actually work.


Want to dive deeper? Check out Living Documentation: Continuous Knowledge Sharing by Design or explore our GitHub repository to see these patterns in action.

Pilot

Book your risk-free pilot programme today

Get Your Custom 4-week Pilot

Limited pilot spots available • Fixed-price programme with custom workflows tailored to your needs

Newsletter illustration

Sign up for our monthly newsletter