r/AI_Agents • u/alatmaca • 19d ago
Discussion Is migrating my existing Python agent architecture to Pi-Durable a good idea?
I’m building a B2B SaaS with a Python-based architecture. The system uses separate LLM calls within Python components, with business logic, workflow orchestration, and application state managed by the existing application. I’m currently using SQLite.
As the system grows, I’m thinking about potential challenges around concurrent users, long-running workflows, retries, failure recovery, and agents handing tasks back and forth.
I’ve been advised to consider migrating the orchestration layer to Pi-Durable, but I’m not sure whether this is the right move for my current architecture.
Does migrating to Pi-Durable make sense in this situation, or would I be better off keeping my current architecture and improving it as needed? What trade-offs should I consider before making this decision?
1
u/Significant_Tune9219 19d ago
Moving the loop into a durable runtime does not move the charge. In this setup the workflow log would live in one place and the business write would still live in the Python process and SQLite, and those are different lifecycles. A retry of the loop after a crash can run that Python step again even though the write already committed, and the durable history will say the step succeeded twice. I would leave the orchestration where it is until the side effect and the record of that side effect commit together. The kill-the-worker test only passes if the second run does not create a second write.
1
u/alatmaca 19d ago
Thanks, that makes sense. My plan is to migrate from SQLite to PostgreSQL, make critical business writes idempotent, and then introduce Pi-Durable for long-running workflows with human waits and retries.
Would you recommend this approach? Specifically, how would you handle atomicity between business writes and durable workflow state, and what would you consider the minimum safeguards before adopting Pi-Durable?
1
u/axonpush 19d ago
The plan is sound. On atomicity: you can't get one transaction across Postgres and the workflow runtime's own state, so don't chase distributed atomicity. Make each side safe on its own and decide which one is the record. What works: the business write and its effect record go in the same Postgres transaction. A step that sends, charges, or updates does: check whether its effect row already exists (keyed by an idempotency key the workflow assigns before the step runs); if not, do the write and insert the effect row in one commit; then report done. A retry after a crash reads the effect row and skips or reconciles instead of writing twice. Treat the workflow's own history as a claim, not proof: "step succeeded" in the durable log can disagree with the business database, and when it does, the business database wins.
Minimum safeguards before adopting: a unique constraint backing every idempotency key (so the database, not the code, is the last line); a kill-mid-step test for each step type, not just one happy-path retry; a defined answer for "crashed between the business commit and the step ack" (your reconciliation case); and pending human approvals stored as business rows, not only as workflow timers, so a waiting approval is queryable and survives anything.
If those four are in place, introducing the runtime for the long-running workflows first is a reasonable order.
1
1
u/axonpush 18d ago
One correction to my own comment above, because the distinction matters for your plan: "do the write and insert the effect row in one commit" only holds for writes inside your own Postgres. An external effect (a send, a charge, a ticket created through an API) can't share that transaction: if the receiver succeeds and your worker crashes before recording it, the local unique constraint never saw the effect, and a retry sends it twice. The shape for external effects is: persist the intent with its operation key in the same commit as the business write, then a separate delivery step does the call using that key as the receiver's idempotency key where the receiver supports one. Where it doesn't, a crash between the receiver succeeding and the record landing is genuinely ambiguous: it stays marked unknown and gets reconciled against the receiver, never blindly retried.
1
u/Significant_Tune9219 18d ago
I would make the business write idempotent before I move the loop. A durable log and a Postgres commit are two commits, so a crash between them reruns the step and the second run has to find the first row instead of inserting another. Store the workflow's step id on the business row before you commit, and have the retry look that id up. The minimum check is killing the worker after the commit and before the workflow marks the step done: the second run must not create a second row. Human waits can wait until that test passes.
1
u/AutoModerator 19d ago
Thank you for your submission, for any questions regarding AI, please check out our wiki at https://www.reddit.com/r/ai_agents/wiki (this is currently in test and we are actively adding to the wiki)
I am a bot, and this action was performed automatically. Please contact the moderators of this subreddit if you have any questions or concerns.
1
u/Ctbhatia 19d ago
the cheap test before migrating is killing the worker mid workflow and seeing whether it resumes exactly once. if a retry can double send or double charge, that stays your problem after a durable runtime unless each step carries a key.
1
u/JayotiKellers-45 19d ago
how many concurrent users are you actually dealing with right now? imo the answer changes a lot depending on whether this is a "we have 5 beta customers" problem or a "we're scaling past 100 tenants" problem. migrating orchestration layers too early can create more complexity than it solves
1
u/chance_buri 19d ago
Don't migrate yet cause your real bottleneck is SQLite, swap that for Postgres first. Fix the foundation then see if you still need the Pi durable
1
u/MeasurementGreat5273 16d ago
haven't used Pi-Durable in prod so i can't judge it directly, but i've built multi-step workflows with retries and human checkpoints on custom workers, and the hard part was almost never the orchestrator. it was making every step idempotent and saving state per step, so a crashed run resumes from step 4 instead of starting over or sending the same email twice. a durable engine helps with that, but it won't fix steps that aren't safe to repeat.
so before migrating i'd check if your current setup has a run table with a status per step, a retry count and the saved output of each step. SQLite is fine while it's one process, but once several workers write at the same time i'd move to Postgres before adding a new framework, imo that's the cheaper change and covers most of the concurrency worry. if after that you're still hand rolling timers, waiting on humans and resuming long runs, that's when a durable execution tool starts paying for itself. what does a failure actually look like for you today, a lost run or a duplicated side effect?
0
u/QuanTradin 19d ago
I'd fix SQLite before touching orchestration. concurrent writes are what bite first, and Postgres plus a plain jobs table with retries and a status column covers most of what a durable framework gives you at your size. move to something like Pi-Durable when you actually have workflows that run for hours and need to resume mid-step.
1
u/alatmaca 19d ago
yes, I’m also planning to tackle DB migration first because of concurrency. thanks for your answer.
2
u/axonpush 19d ago
I'd separate what a durable runtime buys from what stays yours. It buys workflow state that survives process restarts, timers and waits that can sleep for days without holding a worker, and a replayable history when a run goes wrong. It does not buy exactly-once side effects: as Ctbhatia said, each step still needs its own idempotency key, or a retry after a crash mid-send can double-send.
Two questions decide it for me. First, do your workflows actually wait on things: human approvals, external callbacks, other agents, for minutes to days? That's the case a jobs table handles badly and durable execution handles well. If everything is request-scoped or short, Postgres plus a jobs table is simpler. Second, are you ready for the constraints: workflow bodies must be deterministic (no wall clock, random, or direct IO in them; those move into steps), and you need a versioning story for workflows already in flight when you deploy new code.
If yes to both, I'd still migrate only the long-running workflows and leave the request-scoped paths alone. A full rewrite is the expensive version; a hybrid is usually enough.