Documenting System Behavior From Code When Specs Are Out of Date
Code is the source of truth when specifications fall behind reality.

When written specifications no longer describe a system, documenting its behavior becomes an extraction task. The code is the only honest starting point. The work is to recover its rules systematically, preserve the evidence behind them, and make the answers available to people who cannot navigate the implementation themselves.
Why written specs reliably drift away from what code actually does
Code and documentation change at different rhythms. Code is updated continuously as tickets become implementations, while documentation is updated episodically, often after the work is complete. Tests may also come after implementation, if they come at all. This asymmetry makes drift a structural problem.
The Piskala technical report describes requirements that drift, design diagrams that rot, and tests written after the fact, leaving code as the de facto truth. Decisions about the business end up spread across commit messages, while business logic sits deep in service classes. As the architecture changes, the account in the documentation becomes less trustworthy, even if nothing looks wrong with it. Stakeholders cannot trust it, and new engineers cannot rely on it to explain the system. AI coding agents can deepen that gap when they generate code without structured specifications, introducing unstated assumptions faster than documentation can capture them.
What the code actually encodes that specs no longer capture
In most mature codebases, the implementation is the most complete and current record of system behavior. Types, schemas, and interface definitions encode API contracts, while database constraints, validation rules, and migration checks enforce data invariants. Access policies and runtime enforcement contain security rules that a written overview may omit. Module structure and dependency rules show architecture boundaries that an old diagram no longer shows.
Test names, commit messages, and the shape of conditionals can help explain why a rule exists, though the clues may be spread out. Accessing that record across files and repositories is the practical obstacle. Senior engineers often navigate it through knowledge accumulated over time, but that does not make the task inherently dependent on seniority. The Piskala report identifies a concrete use for reconstruction in legacy and brownfield systems, where specifications of existing behavior provide a bridge for preserving required functionality during modernization.
Reading code as a spec through concrete extraction moves
Start with the test suite as a behavior register. Read test names such as “should reject duplicate email”, then inspect the setup blocks for preconditions and the assertion targets for expected results. BDD-style scenarios are especially useful because they express inputs, conditions, and outcomes in executable form. The Piskala report describes BDD as an example of a spec-anchored approach in which specifications evolve with code. A well-maintained suite is therefore a partial spec waiting to be read, rather than a complete account of everything the system does.
Next, trace calls inward from the system’s entry points, including API endpoints, event handlers, and CLI commands. Follow what each trigger invokes so that the resulting map describes user-facing actions. Then read constraints as requirements, inspecting database constraints, validation annotations, and type signatures for rules that the system enforces. A rule such as “Customer email must be unique” can appear in a migration script, jOOQ code, and the domain class.
After the main path is clear, inspect conditionals and error paths for policy. An if-statement rejecting an order with open items, a guard blocking a non-admin, or an error return for a violated invariant describes behavior that belongs in the extracted account. When a rule is surprising, inspect the commit that introduced it and the relevant test names for a ticket reference, bug description, or otherwise hidden constraint. This sequence separates what the system does from the evidence available about why it does it.
Why cross-repository search changes what is findable at all
Reading a handler does not reveal every caller, every API consumer, or every place another service interprets its schema differently. In a distributed system, a repository-local view can leave the behavioral map incomplete even when the local reading is careful. The missing context is part of the behavior being documented. Cross-repository search is therefore a prerequisite for reconstructing that wider behavior, although it is not necessary to begin with a single service.
Searching across repositories makes it possible to find usages of a function, API, or pattern throughout the codebase. Those results reveal both the scope of a behavior and the blast radius of a change. They also expose divergent implementations, such as services that handle the same event differently despite sharing an interface. Ownership and dependency chains become findable without first asking a senior engineer where to look.
How AI agents can accelerate extraction — and what they need to do it reliably
With that context available, AI agents can compress the work of reading call graphs, finding constraints, and drafting behavioral summaries. The agent must describe the evidence already there instead of proposing an implementation that seems to meet a request. The Piskala report characterizes models as strong at pattern completion but poor at recovering unstated intent. A local-file view invites the same unsupported assumptions in documentation that it invites in generated code, producing plausible explanations that may be wrong or stale.
Grounded context gives agents more dependable work to perform. They can turn scenario names and assertions into behavioral summaries, reconstruct use-case flows through service layers, and flag differences between implementations of the same interface. They can also write a first draft of an AGENTS.md or project constitution that sets out entry points, ownership, architecture boundaries, and known invariants. That orientation helps an agent find the system’s structure before extraction begins. It helps the agent read the code while leaving the evidence as the basis for the account.
The Model Context Protocol, introduced by Anthropic, provides an infrastructure layer for supplying that evidence. A codebase knowledge tool exposed as an MCP server can feed grounded, repo-indexed context to agents including Claude Code, Codex, and Cursor without separate integration work for each tool. This makes the context reusable across the agent stack beyond a one-off session. Reliable extraction depends on that combination of structured orientation and access to the full relevant codebase.
Turning extracted behavior into documentation that stays honest
Extraction fails if it produces another independent document that quietly drifts. Written specs can serve as temporary change briefs, but after a release, code and production behavior remain the primary evidence of what the system does. API shape belongs in OpenAPI or protocol schemas, data invariants in types and migration checks, and security rules in access policies and static analysis configs. Copying those facts into prose does not give the prose the same authority.
Narrative still has a place where readers need rationale, an architecture overview, or an onboarding guide. But those accounts should be generated from code when needed. Where possible, executable tests should connect the stated behavior to the implementation so that divergence becomes visible through failure. Domain-oriented language and a clear Given/When/Then structure can make those specifications readable by both people and agents. The goal is enough coverage to remove ambiguity.
For modernization, this connection turns documentation into a change-safety tool. Teams can encode what the system does before they change it, so they can keep the functionality they need while choosing to remove undocumented behavior. That purpose also sets a limit on the extraction effort, since a comprehensive documentation exercise can become overhead that teams abandon. The practical target is the smallest structured artifact that describes the behavior relevant to the change. Version it with the code, retain durable rules in their native forms, and link it to executable tests wherever possible.
Making extracted documentation accessible to people who cannot read the code themselves
A technically accurate artifact still leaves non-engineers dependent on engineers if only engineers can interpret it. New hires can spend weeks digging through repositories and rediscovering what already exists, while senior engineers repeatedly lose focus to answer questions. Extraction only reduces that dependence when its results are queryable without an expert intermediary. The output must therefore provide answers grounded in code.
A natural-language interface over an indexed codebase lets a PM ask what happens when a customer email changes and receive an answer grounded in the validation logic. A support engineer can ask why an order fails when its status is OPEN and receive an explanation traced to the conditional, with a pointer to the code. A new hire can ask who owns the authentication service and get an answer based on module structure and ownership metadata. MCP-based connectors to Jira, Linear, and Confluence can add project history to that code context. The result can explain both the current rule and the decision history that a non-engineer needs to understand it.
The data privacy constraint that makes self-hosting the only viable path for many teams
The same breadth of access that makes extraction useful creates a deployment constraint for sensitive codebases. Code search, context indexing, and AI-assisted extraction require tools to ingest the codebase, while cloud-hosted versions route code through vendor infrastructure. For teams whose obligations require code to remain within company infrastructure, that route is not available. Data sovereignty and compliance requirements make the architecture a condition of adoption. This is not a claim that cloud tools are inherently unsafe, but a recognition that they cannot satisfy every enterprise’s constraints.
Keeping execution on-premises does not keep code inside the environment if the model still runs in a vendor’s cloud. To meet a code-must-stay-inside requirement with self-hosting, both the index and the model must run within company infrastructure. Teams should retain the ability to choose their LLM provider and control where code is sent. Bring-your-own-model options help keep that choice open, but with a self-hosted index that uses an external model API, teams have less data sovereignty than with a fully on-premises stack.
Where this workflow fits into a team's existing process without requiring a methodology overhaul
A team can start extracting behavior without taking up spec-driven development, changing how it handles documentation, or buying new infrastructure. The Piskala report identifies particular value for specifications in long-lived systems with multiple maintainers and in work using AI coding assistants, with brownfield modernization and API-first development among its case studies. Those contexts give extraction a clear purpose, but the starting practice remains local and concrete. It adds evidence to the process a team already has.
Pick one service with a stale spec and read its tests, trace calls from its entry points, and recover its enforced constraints. Produce one structured document that the team agrees describes the service more accurately than the existing spec. The minimum exercise is a half-day task. Once it proves useful, cross-repository search and agent-assisted extraction can make it less costly to do the same work across the system. The code has always been the honest spec of implemented behavior. The change is that tools can now make it systematically readable across the organization, rather than leaving its meaning with the engineers who have held it in their heads the longest.


