WEBVTT

NOTE Sentence-level transcript of https://www.youtube.com/watch?v=DqtmZE6Hl0g

NOTE One cue per sentence. Cue ids are the line anchors on /transcripts/DqtmZE6Hl0g.html. A cue ends where the next begins, or 2 s after its last word.

s1
00:00:02.480 --> 00:00:09.360
In 2026, coding agents will quietly retire their first software platform.

s2
00:00:09.360 --> 00:00:15.000
Not because it's bad, simply because the platform is unnecessary.

s3
00:00:15.000 --> 00:00:16.560
I'm Dominic Tornow.

s4
00:00:16.560 --> 00:00:19.600
I am founder and CEO of Resonate.

s5
00:00:19.600 --> 00:00:27.320
Resonate is a durable execution platform built with minimalism and simplicity as its core technical values.

s6
00:00:27.320 --> 00:00:31.800
And these properties will play a central role in this talk.

s7
00:00:31.800 --> 00:00:37.720
At Resonate, we have a working theory where software engineering is headed.

s8
00:00:37.720 --> 00:00:47.120
General-purpose implementations will increasingly be replaced by bespoke implementations generated on demand.

s9
00:00:47.120 --> 00:00:56.920
Not as a new library, a new framework, or a new platform, but as a minimal extension of the infrastructure that is already in place.

s10
00:00:57.840 --> 00:01:03.280
If this theory holds true, reuse will move upstream.

s11
00:01:03.280 --> 00:01:15.040
Instead of reusing a general-purpose implementation, we will reuse a specification, and we will derive a bespoke implementation from it.

s12
00:01:16.040 --> 00:01:24.720
In fact, we can build many bespoke implementations tailor-made for the infrastructure that is already in place.

s13
00:01:24.720 --> 00:01:27.400
We just have to ask the agent.

s14
00:01:27.400 --> 00:01:32.040
At this point, the prompt is the platform.

s15
00:01:33.040 --> 00:01:38.280
Resonate is a durable execution We have an implementation of the Resonate server.

s16
00:01:38.280 --> 00:01:45.680
We have implementations of the Resonate SDK for TypeScript, Python, Rust, Go, and Java.

s17
00:01:45.680 --> 00:01:51.520
So, we have to ask, what does this new reality mean for us?

s18
00:01:51.520 --> 00:01:57.480
If implementations become generatable, where does our value live?

s19
00:01:57.480 --> 00:01:59.240
And our answer?

s20
00:01:59.240 --> 00:02:04.240
Our value moves from implementation to specification.

s21
00:02:05.600 --> 00:02:09.240
Now, this changes how we think about Resonate.

s22
00:02:09.240 --> 00:02:12.160
The product is no longer the implementation.

s23
00:02:12.160 --> 00:02:15.600
The product is the specification, the protocol.

s24
00:02:15.600 --> 00:02:21.520
And from that protocol, we want to derive multiple server implementations.

s25
00:02:21.959 --> 00:02:26.760
One is the general-purpose Resonate server, our reference implementation.

s26
00:02:26.880 --> 00:02:31.720
Others are implementations built with infrastructure partners.

s27
00:02:31.720 --> 00:02:40.800
For customers and partners, this means durable execution right on top of their existing infrastructure with minimal additional dependencies.

s28
00:02:40.800 --> 00:02:44.520
So, the question is no longer can we build a server?

s29
00:02:44.520 --> 00:02:51.880
The question is, can we repeatedly synthesize trusted servers from the same specification?

s30
00:02:51.880 --> 00:02:54.680
And if so, how?

s31
00:02:58.360 --> 00:03:03.920
When we talk about agentic engineering, we focus all of our attention on verification.

s32
00:03:03.920 --> 00:03:07.400
How do we know the result is correct?

s33
00:03:07.560 --> 00:03:12.160
But today, I want to focus on the specification instead.

s34
00:03:12.160 --> 00:03:18.720
And more importantly, how can agents participate in specifying the system?

s35
00:03:18.720 --> 00:03:22.400
Not just building or verifying it.

s36
00:03:23.519 --> 00:03:31.120
Now, Resonate is partnering multiple infrastructure providers to bring durable executions natively to their technology stack.

s37
00:03:31.120 --> 00:03:41.239
One of them is Synadia, the company behind NATS.io, an open-source messaging system designed for building modern distributed systems.

s38
00:03:41.400 --> 00:03:49.000
For the rest of this presentation, we will use Resonate on NATS.io to explore our agentic engineering practices.

s39
00:03:49.000 --> 00:03:52.720
How do we go from specification to implementation?

s40
00:03:53.160 --> 00:03:57.840
First, we need to level set our mental model.

s41
00:03:58.160 --> 00:04:02.240
This picture is a common view of agent decoding.

s42
00:04:02.240 --> 00:04:07.920
There's an agent, there's a specification, and then there's an implementation.

s43
00:04:07.920 --> 00:04:11.280
And for many applications, that is enough.

s44
00:04:11.280 --> 00:04:15.120
But it is not enough for what we are trying to do.

s45
00:04:15.120 --> 00:04:20.720
Because we are not trying to generate one implementation from a specification.

s46
00:04:23.040 --> 00:04:29.560
We are trying to generate multiple target-specific implementations from the specification.

s47
00:04:29.919 --> 00:04:36.720
So, the specification must not take any aspect of an implementation into account.

s48
00:04:36.720 --> 00:04:42.720
The specification must not assume a concrete database schema or concrete indices.

s49
00:04:42.720 --> 00:04:48.120
The specification must not even assume a relational database with tables and transactions at all.

s50
00:04:48.120 --> 00:04:50.320
It must not assume a key-value store.

s51
00:04:50.320 --> 00:04:52.200
It must not assume weak consistency.

s52
00:04:52.200 --> 00:04:55.360
It must not assume strong consistency.

s53
00:04:56.160 --> 00:04:59.600
The specification must be abstract.

s54
00:04:59.600 --> 00:05:03.640
Only the implementation must be concrete.

s55
00:05:04.800 --> 00:05:10.919
So, we ask the agent to follow the abstract specification and generate a concrete implementation.

s56
00:05:10.919 --> 00:05:19.120
Specifically, at first, we ask the agent, "Build a Resonate server in Rust on top of Postgres."

s57
00:05:20.240 --> 00:05:22.760
And the agent failed.

s58
00:05:23.560 --> 00:05:30.880
The gap between the abstract specification and the concrete implementation was too large.

s59
00:05:31.280 --> 00:05:35.280
The agent generated a system that worked on the happy path.

s60
00:05:35.280 --> 00:05:39.120
It passed the basic tests, but it was not correct.

s61
00:05:39.120 --> 00:05:41.080
It broke on the concurrency.

s62
00:05:41.080 --> 00:05:42.960
It broke on the process failure.

s63
00:05:42.960 --> 00:05:45.040
It broke on the network failure.

s64
00:05:45.040 --> 00:05:50.840
The implementation was closer to a prototype, but not a production system.

s65
00:05:52.720 --> 00:05:54.600
So, we amended the process.

s66
00:05:54.600 --> 00:06:06.720
Instead of asking the agent to jump directly from abstract spec to concrete implementation, we inserted an intermediary artifact, the concrete specification.

s67
00:06:07.200 --> 00:06:12.240
That concrete specification was derived interactively with the agent.

s68
00:06:12.240 --> 00:06:15.760
But, the human was the main driver.

s69
00:06:16.280 --> 00:06:22.440
For Postgres, that meant making target-specific decisions explicit.

s70
00:06:22.440 --> 00:06:28.120
The data schema, the indices, the SQL queries, the transaction boundaries.

s71
00:06:28.120 --> 00:06:34.720
Once those decisions were written down, the agent was indeed able to implement the production system.

s72
00:06:34.720 --> 00:06:37.120
So, this worked.

s73
00:06:37.320 --> 00:06:40.520
But, it also revealed the limitations.

s74
00:06:40.600 --> 00:06:48.120
The agent helped us build the system, but the agent did not help us design the system.

s75
00:06:48.120 --> 00:06:53.880
And if the specification is a reusable product, then that's not enough.

s76
00:06:54.200 --> 00:06:56.560
Now, the next step is obvious.

s77
00:06:56.560 --> 00:06:59.600
Agents have to move upstream.

s78
00:07:00.080 --> 00:07:02.280
But, how?

s79
00:07:04.520 --> 00:07:09.800
When we started building Resonate on Natero, we changed the question.

s80
00:07:09.800 --> 00:07:14.360
We did not ask, "Can the agent build the production system?"

s81
00:07:14.360 --> 00:07:23.480
Instead, we asked, "What does the agent need in order to design the system first, and build the system second?"

s82
00:07:23.600 --> 00:07:28.320
So, we gave the agent access to a deterministic simulation environment.

s83
00:07:28.440 --> 00:07:31.320
And we gave it a different task.

s84
00:07:31.560 --> 00:07:35.040
Do not build the production system.

s85
00:07:35.040 --> 00:07:38.120
Build a simulated implementation.

s86
00:07:38.640 --> 00:07:42.440
The simulated implementation is not the product.

s87
00:07:42.480 --> 00:07:45.560
It is executable design.

s88
00:07:46.040 --> 00:07:52.480
Its purpose is to discover the correct algorithm under partial order under partial failure.

s89
00:07:52.480 --> 00:08:01.000
And once these algorithms are discovered, tested, and verified in simulation, then we ask the agent to write the concrete specification.

s90
00:08:01.000 --> 00:08:05.920
And only then to be asked the agent to write the production implementation.

s91
00:08:06.720 --> 00:08:17.240
So, the process becomes abstract specification, simulation implementation, concrete specification, and then concrete implementation.

s92
00:08:17.520 --> 00:08:21.280
This is the point where the agent moves upstream.

s93
00:08:21.280 --> 00:08:27.680
Humans are still involved in the design process, but now the agent is a driver.

s94
00:08:29.600 --> 00:08:32.400
Two ingredients make this possible.

s95
00:08:32.400 --> 00:08:35.400
Minimalism and simplicity.

s96
00:08:35.440 --> 00:08:39.120
Unfortunately, minimalism and simplicity are not the starting point.

s97
00:08:39.120 --> 00:08:41.080
They are the finish line.

s98
00:08:41.080 --> 00:08:45.280
We spent 3 years making the protocol smaller and simpler.

s99
00:08:45.280 --> 00:08:49.520
Every time we ran into a problem, we asked, "What can we take away?

s100
00:08:49.520 --> 00:08:51.280
What abstraction can we erase?

s101
00:08:51.280 --> 00:08:53.080
What property can we remove?

s102
00:08:53.080 --> 00:08:56.320
What relationship can we break?"

s103
00:08:56.480 --> 00:09:04.720
The result is a very small protocol centered around two objects, a durable promise and a durable task.

s104
00:09:04.839 --> 00:09:13.040
That simplicity matters because even simple concurrent distributed protocol have a complex state and behavior space.

s105
00:09:13.040 --> 00:09:22.360
So, in other terms, implementing even simple protocols on top of a few simple primitives is tough.

s106
00:09:24.120 --> 00:09:26.839
Let's make this concrete with NATS.

s107
00:09:26.839 --> 00:09:31.520
NATS gives us a small set of primitives we can build on.

s108
00:09:31.520 --> 00:09:37.839
Queues, a key-value store, and delayed or scheduled messages.

s109
00:09:38.280 --> 00:09:40.480
These are not resonate concepts.

s110
00:09:40.480 --> 00:09:44.120
These are the concepts of the target platform.

s111
00:09:44.280 --> 00:09:53.360
So, the design question becomes how can we express the resonate protocol using only these primitives?

s112
00:09:54.480 --> 00:09:57.520
Let's focus on the key value store.

s113
00:09:57.520 --> 00:10:00.000
The key value store is versioned.

s114
00:10:00.000 --> 00:10:06.560
We create a key with value foo, then we update it to bar, then we update it to baz.

s115
00:10:06.560 --> 00:10:10.960
So, the latest value is baz at version two.

s116
00:10:11.160 --> 00:10:17.040
Most of the time, when we read the key, that is exactly what we get.

s117
00:10:17.040 --> 00:10:19.400
A fresh read.

s118
00:10:19.400 --> 00:10:23.560
And if all reads were fresh, the design would be straightforward.

s119
00:10:23.840 --> 00:10:27.840
But sometimes, the read is stale.

s120
00:10:29.000 --> 00:10:33.800
Here, the latest value is still baz at version two.

s121
00:10:33.800 --> 00:10:38.160
But the read returns foo at version zero.

s122
00:10:38.160 --> 00:10:40.240
That is not corruption.

s123
00:10:40.240 --> 00:10:43.920
That is not a bug in the key value store.

s124
00:10:43.920 --> 00:10:50.280
That is a valid read under the consistency model of the target platform.

s125
00:10:52.560 --> 00:10:59.680
And that matters because our implementation cannot be correct only when the target behaves conveniently.

s126
00:10:59.839 --> 00:11:05.480
The implementation has to be correct when the target behaves legally.

s127
00:11:06.320 --> 00:11:11.080
So, the simulation environment has to expose exactly this kind of behavior.

s128
00:11:11.080 --> 00:11:19.200
Fresh reads, stale reads, and the version information that tells us which world we are in.

s129
00:11:20.920 --> 00:11:25.720
Unfortunately, we do not know the read was stale simply by reading.

s130
00:11:25.720 --> 00:11:29.640
We will find out later when we try to write.

s131
00:11:29.640 --> 00:11:31.680
Here, we read version zero.

s132
00:11:31.680 --> 00:11:34.960
So, we try to update version zero.

s133
00:11:34.960 --> 00:11:36.960
But, the key has already moved on.

s134
00:11:36.960 --> 00:11:39.080
The right fails.

s135
00:11:39.080 --> 00:11:46.960
That is the moment the target tells us the word you saw is not the current word.

s136
00:11:48.400 --> 00:11:57.040
Building always correct applications on top of a concurrency model that allows occasional stale reads is not simple.

s137
00:11:57.040 --> 00:11:58.480
Not for humans.

s138
00:11:58.480 --> 00:12:00.600
Not for agents.

s139
00:12:00.600 --> 00:12:03.520
So, how do we set up our agent for success?

s140
00:12:03.520 --> 00:12:09.840
What tools does our agent need to ace this task instead of falling flat on its face?

s141
00:12:11.880 --> 00:12:14.280
Agents thrive on feedback.

s142
00:12:14.280 --> 00:12:18.240
Immediate unambiguous feedback.

s143
00:12:18.240 --> 00:12:22.120
Not just feedback that shows this went wrong.

s144
00:12:22.120 --> 00:12:26.760
Feedback that shows why and how this went wrong.

s145
00:12:27.680 --> 00:12:30.120
What stale value was returned?

s146
00:12:30.120 --> 00:12:32.040
What logic was triggered?

s147
00:12:32.040 --> 00:12:33.480
What right failed?

s148
00:12:33.480 --> 00:12:37.720
And which invariant broke because of that?

s149
00:12:38.240 --> 00:12:42.160
So, we built a deterministic simulation testing environment in Python.

s150
00:12:42.160 --> 00:12:48.760
And inside that environment, we simulated the parts of Nuts IO we depend on.

s151
00:12:48.760 --> 00:12:53.360
Here, for example, is a simulated key-value store.

s152
00:12:53.800 --> 00:12:57.680
It keeps a full version history for every key.

s153
00:12:57.680 --> 00:13:02.400
On get, the simulated store sometimes returns the latest version.

s154
00:13:02.400 --> 00:13:10.280
But, sometimes, controlled by the deterministic random generator, the store returns an older version.

s155
00:13:10.480 --> 00:13:14.000
On update, the store enforces optimistic concurrency.

s156
00:13:14.000 --> 00:13:18.160
The right only succeeds if the version you read is still the latest version.

s157
00:13:18.160 --> 00:13:20.960
Otherwise, it raises.

s158
00:13:23.000 --> 00:13:30.000
This gives the agent a store that behaves like the real store in the ways that matter for correctness.

s159
00:13:30.000 --> 00:13:37.080
But, unlike the real target, the simulation is deterministic, it's repeatable, and it's inspectable.

s160
00:13:37.720 --> 00:13:45.280
So, when the agent writes the wrong algorithm, we can reproduce the exact execution that broke it.

s161
00:13:45.280 --> 00:13:50.760
And the agent can repair the algorithm against that trace.

s162
00:13:53.160 --> 00:13:59.880
But, deterministic simulation does more than just inject stale reads.

s163
00:14:02.200 --> 00:14:07.640
It lets us expose facts the real platform hides.

s164
00:14:07.640 --> 00:14:10.600
We call this the forbidden fruit.

s165
00:14:10.600 --> 00:14:16.880
In production, when you read from the key-value store, you only get the value and the version you observed.

s166
00:14:16.880 --> 00:14:20.760
You do not get to know whether that read was fresh or stale.

s167
00:14:20.760 --> 00:14:24.600
You do not get to see the latest value you missed.

s168
00:14:24.600 --> 00:14:30.720
And you shouldn't get that information because real code cannot depend on it.

s169
00:14:30.880 --> 00:14:34.880
But, in simulation, we can record it.

s170
00:14:34.920 --> 00:14:38.120
Here, every get emits a trace event.

s171
00:14:38.120 --> 00:14:41.760
If the read is fresh, the trace says, "This was fresh."

s172
00:14:41.760 --> 00:14:44.760
If the read is stale, the trace says, "This was stale.

s173
00:14:44.760 --> 00:14:49.200
This is what you got, and this is what the latest value was."

s174
00:14:49.200 --> 00:14:57.000
That information is forbidden to the algorithm, but it is incredibly useful to the agent.

s175
00:14:57.240 --> 00:15:04.360
It lets us explore failures in terms the agent can act on.

s176
00:15:05.839 --> 00:15:09.120
Now, this is what a trace event looks like.

s177
00:15:09.120 --> 00:15:12.960
The production code only receives the result.

s178
00:15:12.960 --> 00:15:17.240
It sees the promise was pending.

s179
00:15:17.240 --> 00:15:20.920
This is all the real platform would tell us.

s180
00:15:20.920 --> 00:15:25.280
But, the simulation also records the type of the read.

s181
00:15:25.280 --> 00:15:28.200
This read was a stale read.

s182
00:15:28.200 --> 00:15:32.560
And it records the latest value that was hidden from the algorithm.

s183
00:15:32.560 --> 00:15:37.840
The latest value says the same promise is already settled.

s184
00:15:37.920 --> 00:15:45.200
That difference is exactly the kind of fact an agent needs when it's debugging a distributed algorithm.

s185
00:15:45.200 --> 00:15:54.640
Not just the invariant failed, but the invariant failed because the algorithm made a decision from a stale view of the world.

s186
00:15:54.640 --> 00:16:04.800
Again, the algorithm is not allowed to depend on this information, but the agent is allowed to use it to explain why the algorithm it designed was wrong.

s187
00:16:04.800 --> 00:16:07.840
Cause and effect becomes visible.

s188
00:16:08.000 --> 00:16:15.120
The agent does not just learn that the system is wrong, it learns why the system is wrong.

s189
00:16:16.680 --> 00:16:21.920
And with this approach, the agent was able to close the gap.

s190
00:16:22.240 --> 00:16:32.280
First, the agent built a proof of concept in the deterministic simulator, verified by fuzz testing.

s191
00:16:33.680 --> 00:16:43.440
From the proof of concept, the agent derived the concrete specification, where we already knew the algorithm was correct.

s192
00:16:45.320 --> 00:16:52.360
And like before, from the concrete specification, the agent derived an implementation.

s193
00:16:52.960 --> 00:17:00.320
Deterministic simulation lets agents participate in the design, not just in the implementation.

s194
00:17:03.360 --> 00:17:08.400
Humans are still in the design process, but this time the agent is the driver.

s195
00:17:08.400 --> 00:17:12.839
From a single abstract specification, the agent designed and built the platform.

s196
00:17:12.839 --> 00:17:18.439
Via simulation to concrete specification to concrete implementation.

s197
00:17:18.560 --> 00:17:24.319
The prompt is the platform and the specification is the product.

s198
00:17:24.319 --> 00:17:25.720
Thank you very much for watching.

s199
00:17:25.720 --> 00:17:29.200
If you have any questions, please don't hesitate to reach out.

s200
00:17:29.200 --> 00:17:32.480
You will find me in Resonate's Discord.
