If there's one thing I definitely don't want AI in the middle of, it's communication with other people. The potential for misunderstandings due to hallucination in summaries, both on my end and the recipient's, scares me. There were some pretty bad examples with Apple News.
Accuracy matters, especially when communicating with customers or between managers/employees, and I can imagine many kinds of scenarios where this goes wrong.
I'm honestly curious why Apple (and other OS vendors like MS and various Linux distributions) still feel the need to tweak their UIs many, many years after having reached maturity.
How many iterations does it take before you get it right?
I get that there's a certain sense of fashion to it, but so often these changes are either neutral or worse, and it just seems so pointless. I don't see any concrete benefits of this year's UI design over what was already there 10-20 years ago.
Operating systems from the biggest companies are rather stable and feature rich beneath for years now but that doesn't make these attractive in long term. Certainly not for the daily consumers. So they introduce all sorts of meaningless "featurettes" and UI/UX changes to pretend they work hard and thoughtfully on their products to create these never-ending "exciting experiences".
It's not about polishing to get it right nowadays but rather making a change for sake of changes because that looks good in terms of marketing.
As for this particular Apple case with all "26" versions and Liquid Glass: the backlash they got in June puts their actions along Microsoft's with Windows 8/8.1.
> I'm honestly curious why Apple (and other OS vendors like MS and various Linux distributions) still feel the need to tweak their UIs many, many years after having reached maturity.
its hard to market something like an os to consumers and devs without some large noticeable changes
though one then has to wonder, why do we need a new os every year...
If AI coding agents were actually any good, you could preface your prompt with "attempt the following task four times in parallel" and that would be it.
Saying the quiet part out loud, but yeah this is a very weird relationship where you have a partner who wouldn't be outright enthusiastic about you pursuing something you really want to, so long as your family can still live comfortably.
Even in the absence of other work/history, these are literally slides from a talk they gave on the topic, promoting a tool (https://deps.dev) to help more people understand and approach the problem.
Durable execution is best done at the level of a language implementation, not as a library.
A workflow engine I recently built provided an interpreter for a Scheme-based language that, for each blocking operation, took a snapshot of the interpreter state (heap + stack) and persisted that to a database. Each time an operation completes (which could be after hours/days/weeks), the interpreter state is restored from the database and execution proceeds from the point at which it was previously suspended. The interpreter supports concurrency, allowing multiple blocking operations to be in progress at the same time, so the work to be done after the completion of one can proceed even while others remain blocked.
The advantage of doing this at the language level is that persistence becomes transparent to the programmer. No decorators are needed; every function and expression inherently has all the properties of a "step" as described here. Deterministic execution can be provided if needed. And if there's a need to call out to external code, it is possible to expose Python functions as Scheme built-ins that can be invoked from the interpreter either synchronously or asynchronously.
I see a lot of workflow engines released that almost get to the point of being like a traditional programming language interpreter but not quite, exposing the structure of the workflow using a DAG with explicit nodes/edges, or (in the case of DBOS) as decorators. While I think this is ok for some applications, I really believe the "workflow as a programming language" perspective deserves more attention.
There's a lot of really interesting work that's been done over the years on persistent systems, and especially orthogonal persistence, but sadly this has mostly remained confined to the research literature. Two real-world systems that do implement persistence at the language level are Ethereum and Smalltalk; also some of the older Lisp-based systems provided similar functionality. I think there's a lot more value waiting to be mined from these past efforts.
We (AutoKitteh) took a different approach to provide durable execution at the language level:
We are using Temporal as a durable execution engine, but we built a layer on top of it that provides a pure Python experience (JS coming soon).
This is done by hooking into the AST and converting functions that might have side effects to Temporal activities. Of course it's deeper than that.
For the user, it's almost transparent. Regular Python code (no decorators) that you can run as Duralbe workflow in our system or as regular Python.
You can look at the open-source, and we have a blog explaining how it's implemented.
I see it being a trade-off between how explicit the state persisted for a workflow execution is (rows in a database for Temporal and DBOS) vs how natural it is to write such a workflow (like in your PL/compiler). Given workflows are primarily used for business use-cases, with a lot of non-determinacy coming from interaction with third-party services or other deployments, the library implementation feels more appropriate.
Though I am assuming building durability at a language-level means the whole program state must be serializable, which sounds tricky. Curious if you could share more?
There's certainly a tradeoff between the two approaches; a simpler representation (list of tasks or DAG) is easier to query and manipulate, at the cost of being less expressive, lacking features like loops, conditionals, etc.
In the workflow engine I described, state is represented as a graph of objects in memory; this includes values like integers/strings and data structures like dictionaries/lists, as well as closures, environments, and the execution stack. This graph is serialised as JSON and stored in a postgres table. A more compact binary representation could be added in the future if performance requirements demand it, but JSON has been sufficient for our needs so far. A delta between each snapshot is also stored in an execution log, so that the complete execution history is stored for auditing purposes.
The interpreter is written in such a way that all object allocation, object manipulation, and garbage collection is under its control, and all the data needed to represent execution state is stored in a manner that can be easily serialised. In particular, we avoid the use of pointers to memory locations, instead using object ids for all references. So the persistent state, when loaded, can be accessed directly, since any time a reference from one object to another needs to be followed, the interpreter does so by looking up the object in the heap based on its id.
Non-deterministic and blocking operations (including IPC receives) are handled outside of the evaluation cycle. This enables their results to be explicitly captured in the execution log, and allows for retries to be handled by an external mechanism under control of the user (since retrying can be unsafe if the operation is not idempotent).
The biggest win of using a proper language for expressing the workflow is the ability to add arbitrary logic between blocking operations, such as conditional tests or data structure manipulation. Any kind of logic you might want to do can be expressed due to the fact the workflow language is Turing-complete.
That's really interesting! It does seem that this is identically semantically to the library approach (as the logic your interpreter adds around steps could also be added by decorators) but is completely automatic. Which is great if the interpreter always does the right thing, but problematic/overly magical if the interpreter doesn't. For example, if your problem domain has two blocking operations that really form one single step and should be retried together, a library approach lets you express that but an interpreted approach might get it wrong.
As soon as you do that, you tie in your state-preserving storage (a database?) with your language as your "programming environment", and it becomes harder to decouple them (or the design becomes overly complex with configurable implementations of a state-database interface).
So, I don't think this should be at the language level. Potentially at a programming environment level, which includes configuration for such environment, but separation of concerns is screaming loudly in my head :)
Reminded me of the Restate idea[1], discussed here[2] recently, except they do it as a library. An excerpt:
To persist intermediate steps (line 8), handlers use the SDK (ctx.run), which sends the event to the log and awaits the ack of the conditional append to the event’s execution journal. On retries, the SDK checks the journal whether the step’s event already exists and restores the result from there directly.
Though I think I agree with your point that it would be better to have this even more integrated than "just" a library. Perhaps something like how you can override the global memory allocator in C and similar languages, to avoid a tight coupling to the persistence layer.
A contributor in this space that I always thought was under-appreciated is Amazon SWF + Flow Framework. It's an older technology and SWF itself is deprecated, but it is a weird middle ground here in that it makes language-level modifications to inject workflow state persistence and coordination into Java code (via AspectJ). The original concept was to build a "distributed CPU" (ex see https://docs.aws.amazon.com/amazonswf/latest/awsflowguide/aw...).
SWF was the predecessor for AWS Step Functions. IIUC the lesson learned from SWF was that it was just too flexible, and imposing a more limiting set of constraints was both easier for the programmer to use & reason about, and made for simpler and faster execution.
I was playing with resumable execution and I agree that language-level support would be immense improvement. That said, I was able create library that would take javascript code and execute it line-by-line where after each line the state is stored but user needs to use state object to store all intermediate results which are persisted. But there is still problems if the computation fails during on some line. We can retry it but if it broke the entire execution state then we have a problem.
I wonder how to achieve the transaction-like (commit/rollback) behavior that will works across boundaries. Doing it on language-level is the way. Compiler/interpreter can handle all the state serializations.
This comment reminds me that despite having built lots of cool stuff in my career, that in reality I've just barely done anything that can be called "software engineering".
I dream of a world in which more investment is put into creating better programming languages and runtime environments than trying to use LLMs as a way of coping with the complexities of current systems.
reply