Hacker News
Pi Durable
lukebuehler
|next
[-]
The main reasons are:
1) they are "durable", i.e. easier to make long-running in an unattended way, and easier to implement recovery, monitoring, etc
2) separating the harness from the compute brings safety and scaling benefits
3) easier to make multi-player.
the_mitsuhiko
|root
|parent
[-]
lukebuehler
|root
|parent
|next
[-]
I like your structured concurrency approach with tasks, which is similar to how I do it in Lightspeed too.
Also, the durable state implementation as documents is elegant! Question, though: why directly write/read to the store, why not abstract it and do more of a reducer/redux pattern and hide the persistence of the documents?
the_mitsuhiko
|root
|parent
|next
[-]
We tried so many things. At one point it pulls in so much more complexity. At one point we had half of automerge's proxy system in there. In the end we felt like this is a reasonable line to draw, but we will see!
badlogic
|root
|parent
|previous
[-]
The good thing is that this more low level API can be easily papered over with a nice sugary thing.
rsalus
|next
|previous
[-]
also, I see most of the durability promise comes from persisting JSON documents locally and minimizing the amount of context/data kept in-memory, even during SQLite mode. while this makes sense, my own experiments with a process that relied on a JSONL-based event store have led me to prefer keeping things in-memory to avoid all the friction with I/O.. am I crazy for preferring just a straight .db file being persisted?
ireadmevs
|next
|previous
[-]
Woah, that big of a difference when it comes to token counting?
vmg12
|next
|previous
[-]
badlogic
|root
|parent
[-]
``` const SyncToPostgres = defineTask<{ entryId: string }, { phase: "send" }, void>({ kind: "app.sync-postgres", version: 1, initial: () => ({ phase: "send" }), phases: { send: async (task, runtime, context) => { const entry = await runtime.read(/* the entry */); await postgres.upsert("messages", { id: task.input.entryId, ...entry }); // idempotent by id await runtime.commit(() => ({ status: "terminal", outcome: { status: "completed" } }), context); }, }, });
await root.commit(async (tx) => {
const id = await tx.entry(AssistantEntry, answer);
await tx.createTask(SyncToPostgres, { entryId: id }, { ownership: { kind: "conversation" },
background: true });
}, context);
```The commit on the root conversation picks out the last agent answer id from the transcript, and durably schedules a task that then syncs it to postgres. inside the task, you fetch the answer by id and send it over to postgres indempotently.
What's missing here is sugar, basically a hook that runs inside each commit so the outbox write is atomic with the state change, with ordered delivery, and possibly a durable change feed with cursors.
Thanks for the input!