Outcomes as Code: Form follows Function
Stay Updated with Agentic Labs
Join Our Mailing List
When Amazon is building something new, it doesn’t start with code, product requirements, or specs. It starts with the press release.
That press release effectively provides a functional description of the software in natural language. It defines what the software does but focuses on the end state–the intended outcome for the customer–rather than the intermediary implementation details. Only once the functional description is nailed down can the product, design, and engineering teams define the UX and code.
This makes developers translators. They translate the idea–the functional outcome–into code into product. But is this now needed? If we create a comprehensive functional description, can we use AI to produce the software app that the description implies? We think so.
Function vs. Implementation
Most product development doesn’t work like Amazon. Instead, implementation dominates. Teams spend their time defining the how and the what: how to structure the database schema, what specific programming languages and frameworks to use, how to design the system architecture, and What APIs and integrations to implement.
These all matter, but only in relation to the function. We care about what software does, so developers must constantly run this translation. Will the implementation change in the code give them the functional change in the product? Good developers can do this like a polyglot, understanding intuitively how an implementation change will translate to functionality and how it will cascade.
This translation process between functionality and implementation is a balancing act. Developers must simultaneously consider the software's high-level functional goals and the low-level implementation details required to achieve those goals. This dual focus can lead to a form of cognitive dissonance, where the elegance of the functional description clashes with the often messy reality of code implementation.
This translation is not a one-time event but a continuous process throughout the development lifecycle. Developers must constantly reassess whether their implementation choices align with the desired functionality as the software evolves. This ongoing reconciliation can lead to feature drift, where the implemented product gradually diverges from the original functional vision.
Any help we can give developers in this task can reduce this burden and lead to better software. AI is ideally suited for this, and we already see it take the role of intermediary translator. But we think it can go much further.
The present: LLMs as translators
“The practical upshot of all this is that if you stick a Babel fish in your ear you can instantly understand anything said to you in any form of language.”
- Douglas Adams, The Hitchhiker's Guide to the Galaxy
LLMs have become a Babel fish. We’re not quite sticking them in our ears just yet, but LLMs currently bridge the gap between human intent and machine instruction. The translation works in three clear ways.
First, natural language to code: “Write me an API endpoint in Rust?”
use actix_web::{web, App, HttpServer, Responder}
async fn hello() -> impl Responder {
"Hello, World!"
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.route("/hello", web::get().to(hello))
})
.bind("127.0.0.1:8080")?
.run()
.await
}
Second, code to code translation: “Please translate this Rust code to TypeScript.”
import express from 'express';
const app = express();
const port = 8080;
function hello(): string {
return "Hello, World!";
}
app.get('/hello', (req, res) => {
res.send(hello());
});
async function main() {
try {
app.listen(port, '127.0.0.1', () => {
console.log(`Server running at http://127.0.0.1:${port}`);
});
} catch (error) {
console.error('Error starting server:', error);
process.exit(1);
}
}
main();
Third, code to natural language: “What does the above Rust code do?”
This Rust code sets up a simple web server using the Actix Web framework. Here's a breakdown of what it does:
- It imports necessary components from the `actix_web` crate.
- Defines an asynchronous function `hello()` that returns a simple "Hello, World!" string as a response.
- The `main()` function is marked with `#[actix_web::main]`, which sets up the Actix runtime.
- Inside `main()`, it creates a new `HttpServer`:some text
- Configures a new `App` instance.
- Adds a route for "/hello" that responds to GET requests by calling the `hello()` function.
- Binds the server to the local address "127.0.0.1" on port 8080.
- Runs the server asynchronously.
In essence, this code creates a web server that listens on localhost:8080 and responds with "Hello, World!" when you access the "/hello" endpoint via a GET request.
This is all extremely valuable. However, all these ideas are still embedded within a narrow definition of code. This describes "a specific rust program that performs the function," but what if we wanted to create "a program that performs the function"? In this example, you must still understand Rust to understand what is written. We could take this further and get an output like this:
- Creates a new HTTP Server with a "/hello" route that responds to GET requests with “Hello, World!”.
- Binds the server to the local address "127.0.0.1" on port 8080.
- Runs the server asynchronously.
We are left with a functional description of the program that could be satisfied by the original program, but also any program in any language that will respond to the "/hello" route with "Hello, World!" This description is sufficient to ensure consistent behavior regardless of the underlying implementation. AI can produce descriptions like this today, but the challenge is to understand how to consistently create these functional descriptions that capture the "right" constraints.
The future: Outcomes as code
We want to be in a position where developers no longer need to translate between functional descriptions and implementation details. Instead of starting with code, we begin with a comprehensive functional description–much like Amazon's press release, but more structured and detailed.
Let’s say we’re building a hydration app. We put together something akin to a PRD, structured with the same definition and clarity as would be needed by a human engineering team:
- Objective: Create a comprehensive hydration-tracking app that motivates users to maintain optimal hydration levels through personalized recommendations, social features, and integration with health devices.
- Features: Implement multi-factor authentication, real-time hydration tracking with customizable goals, integration with popular smartwatches and fitness trackers, social challenges and leaderboards, and a machine learning algorithm for personalized hydration recommendations based on user activity, climate, and physiological data.
- UX Flow: Design an onboarding process, data input methods (manual and automatic via device integration), a customizable dashboard with data visualization, and a gamified reward system to encourage consistent hydration habits.
- Constraints: Adhere to GDPR and HIPAA compliance for data protection, optimize for low battery consumption on mobile devices, and ensure offline functionality with data syncing
In reality, you'd go even deeper–the more detail, the better. But the focus is entirely on functionality. This technically focused document guides the implementation details, where developers play a crucial role in determining a feasible solution. Their input ensures the PRD incorporates all essential elements while excluding unintended behaviors, bridging the gap between product vision and technical implementation. AI can then assist in parsing and implementing these well-defined requirements.
This becomes even more powerful when changes need to be made. Instead of taking a functional description from a product manager and figuring out how it integrates into the codebase, you just need to integrate your new PRD with the existing PRD that defines the whole application. There is no translation because everyone is speaking the same language.
You're now working in a domain where engineers and non-technical people can interact rather than having a lossy translation step between each. Team collaboration improves as everyone, from product managers to designers to engineers, can work from the same functional description.
Functionality and performance are decoupled. With the functional definition as the source of truth, the performance can be optimized as a secondary function. If the performance is not possible with the given functional spec, the spec can be changed consciously to allow for better performance.
This is much better than the current system, in which developers often make these decisions unilaterally without fully understanding the product reasoning and make unintended compromises. A great example is UX. UX designers create visual elements but require engineers to translate those into frontend code that meets all the app's functional requirements. Developers might make subtle changes that don’t seem essential but shift how the UX and visual elements work. The planning process diverges from the actual implementation, making tracing changes very hard.
If we already have the functional requirements well defined, then adding these visual elements would be sufficient to fully define the implementation.
If it turns out that something in the visual design was incompatible with the functional description, then these pieces could be highlighted. Then, the decision could be made to alter the functional description or the visual element to bring them back into alignment.
With this evolving functional description, the benefits of the "Outcomes as Code" approach become clear:
- Alignment: All team members have a shared understanding of the app's functionality.
- Traceability: The evolution of the hydration app from a basic tracker to a more personalized and engaging tool is documented in one place.
- Efficiency: The AI can generate updated code for all new features in minutes.
- Reduced miscommunication: There's less chance of misinterpreting how features should work or how the visual element should behave, as it's clearly described in the shared document.
- Faster iterations: The team can quickly assess how the new features impact the overall user experience without getting bogged down in implementation details.
This shared, evolving description serves as a living document of the hydration app's functionality. As development progresses, all work artifacts—updated requirements, visual designs, user feedback, and performance metrics—are integrated. This ensures that subsequent updates and iterations build upon a current, comprehensive understanding of the product, eliminating the need to start from scratch with each modification. The result is a dynamic blueprint that maintains a clear link between the original vision and the current implementation, enabling more efficient and cohesive development cycles.
This approach elevates programming to a more abstract level. More importantly, it pulls everyone into the same process–developers, designers, and PMs. Everyone is now building the app in the same language and contributing directly to a shared, evolving blueprint of the product.