AI agents can now write a working module in minutes, and increasingly they can write the proofs that it is correct too. But an agent builds what you asked for. If what you asked for is vague, incomplete, or wrong, you get well-tested, confident, wrong software.
As agents take over more of the coding, the skill that matters is saying precisely what a system must do and checking that what was built does it. Software engineers have long separated two questions: are we building the system right? (verification) and are we building the right system? (validation). Agents are getting good at the first, provided someone gives them a spec. This course teaches you to write that spec, show that it is the right one, and hold agents to it: to build the right system right. You will own the requirements and specifications; agents will write the code and proofs; a verifier will decide what gets accepted.
Industry is already moving this way. Spec-driven development tools such as Kiro and GitHub Spec Kit have agents work from written requirements and specifications instead of one-line prompts. This course goes two steps further: you will prove that code meets its specification, not just test it, and you will learn to show that the specification says what people actually meant.
Here is a request you could implement in five minutes:
Rank the class roster by grade, highest first.
Here is a first attempt at saying precisely what that means, written in Verus, a verifier for Rust. The ensures clause is the specification: it states what must be true of the output out for every possible roster.
/// Rank the class roster by grade, highest first.
fn rank(roster: Vec<Student>) -> (out: Vec<Student>)
ensures
// highest grade first
forall|i: int, j: int| 0 <= i < j < out.len() ==> out[i].grade >= out[j].grade,
See anything wrong with it? Take a minute before you read on. Think about what an implementation could return that meets this spec but that nobody would want. Think about the cases the one-line request never mentions.
{
Vec::new() // verified!
}
Here is a spec that settles the first two questions (the third is still open):
/// The students in `s` whose grade is `g`, in the order they appear in `s`.
spec fn with_grade(s: Seq<Student>, g: u32) -> Seq<Student> {
s.filter(|x: Student| x.grade == g)
}
/// Rank the class roster by grade, highest first.
fn rank(roster: Vec<Student>) -> (out: Vec<Student>)
ensures
// nobody lost, nobody added, nobody duplicated
out@.to_multiset() == roster@.to_multiset(),
// highest grade first
forall|i: int, j: int| 0 <= i < j < out.len() ==> out[i].grade >= out[j].grade,
// students with equal grades keep their roster order
forall|g: u32| with_grade(out@, g) == with_grade(roster@, g),
None of these questions is hard. The point is that the request didn't answer them, and an agent asked to implement it won't ask them either: it will pick an answer silently.
The course is organized around three skills, in the order you will use them: write a spec, test it until you trust it, then hold code to it.
Requirements written in English are full of ambiguities and gaps, like the roster request. You will learn to find them and to turn requirements into precise, checkable specifications.
A verifier can prove that code meets a spec, but nothing checks that the spec says what people meant. The first-attempt roster spec is a perfectly good spec; it just isn't the one anyone wanted. You will learn to produce evidence that a spec captures intent, evidence that convinces your team and not just the verifier.
Once you trust the spec, it becomes the bar that agent-written code must clear. A spec is mathematics, not code in any particular language, and you can enforce the same spec in different ways, each with a different guarantee:
Why does the difference matter? In 2015, researchers trying to formally verify TimSort, the sorting algorithm in the Java and Python standard libraries, found a bug that years of use and testing had missed: it shows up only on inputs far larger than anyone thought to test. You will learn what each kind of evidence guarantees, what it asks you to trust, and how to choose: test everything, and prove what matters most.
Along the way you will learn:
The course is built around a team project on a real system with real requirements. Each team takes one application from NASA's open-source core Flight System (cFS), the flight software framework used on a number of NASA missions, and works from the requirements NASA published with it.
Real requirements have the same problems as the roster request. One cFS application must start a recovery procedure when a monitored condition "has failed for the table-defined consecutive number of times." Suppose the condition is checked three times and the results are Fail, then Error, then Fail. Is that two consecutive failures, or one? The requirements' own definition of "consecutive" says one. The C code says two. Which is right? In this course, the code does not get to decide. You do, with evidence.
Over the semester your team will:
Prerequisites are CS 2120 (Discrete Mathematics and Theory 1) and CS 3140 (Software Development Essentials). No prior experience with Rust, Verus, or formal methods is assumed.
If you expect to spend your career working alongside AI agents and want to be the person who knows exactly what the software is supposed to do, and can show that it does, this course is for you.