Ian Macartney

Ian Macartney

@ianmacartney.bsky.social

Friendly engineer at Convex.dev

74 followers24 following61 posts

I feel clever subbing `return await fn()` with `return fn()` But gotchas don't seem worth it anymore • Breaks try/catch/finally expectations • Loses stack trace context My default is now to always await. Change my mind.

Documentation for the Agent component is live 🎉 -> docs.convex​.dev/agents Odd coincidence that there's ~2300 lines of documentation & ~2300 lines of example code 🤔 ...maybe more surprising there's fewer lines of React? @​convex-dev/agent

Rate limiting LLM chat per-user with `useRateLimit` const { status } = useRateLimit(api.rl.getRateLimit) Full code in 🧵 Algorithms: Sending messages: fixed window Token usage: token bucket courtesy of @​convex-dev/rate-limiter + @​convex-dev/agent (components)

Recent talk on agents & agentic workflows, as well as my Convex Agent Component. Takes: • Agentic := prompting + routing • Prompting is input -> LLM -> output • Routing has code at every boundary (...even if an LLM "decides" what to do next) Full 📺🔗->🧵

Streaming LLM text using websockets + client smoothing - no HTTP necessary! Agent v0.2.1 is out! Repo & release notes in 🧵 - Streaming text react hook + server fns - Client-side smoothing hook - Optimistic update helpers

wdyt? const messages = useThreadMessages( api​.foo.listMessages, { threadId }, { initialNumItems: 10, stream: true }, ); const sendMessage = useMutation( api​.foo.generateText, ).withOptimisticUpdate( optimisticallySendMessage(api​.foo.listMessages), );

Not all embeddings are created equal. Some represent more meaningful context. I struggled with AI Town to efficiently do vector search that also included a 1-10 "importance" Last night I figured out a way to prioritize some embeddings over others using a "bias" feature 🧵

My original thought was to just scale all the values, but vector search normalize the vectors for -1:+1 scores. The trick is to add an extra number ("feature") to the embedding. [...1536 numbers, <X>] Then query with [...1536 numbers, 0]. How it works: 🧵

For an importance of x (0 to 1): 1. Normalize the existing vector to (1-x) and add √.x 2. Search with [...embedding, 0]. e.g.: Say we have an embedding of 2 numbers [.6, .8] For 50% importance: [.3, .4, .707] For [.6, .8] we used to get 1.0. Now we get .6*.3 + .8+.4+0 = .5🎉

I do RAG via hybrid text/vector search using reciprocal rank fusion (the one-weird-trick of hybrid search imo) for my new Agent framework/component. It's open source and the code is remarkably simple, if you're looking for an example for yourself.

Exciting news for Agent Workflow front: 🪨Durable Workflows🪨: Orchestrate steps async with retries, checkpointing and more, using Inngest-style syntax 🤖 Agent Framework 🤖: Define agents and use threaded memory (can hand off between agents), with hybrid text/vector search. 🧵

I made some cartoons today for an article I'm writing. This one is me when I'm too deep in a project that's gone off the rails, complete with alt text. Should I ship it or make a "better" version to make it "more professional"?

What I meant to say was "correctness & reliability" - mental blip when I first posted this. The reality is that while everyone's talking about how cool their app is in the happy path, actually handling failure is near impossible when you're using leaky abstractions.

Ian Macartney@ianmacartney.bsky.social

Thoughts on about safety & reliability in the age of agentic flows and durable workflows: Details in 🧵, but tl;dr: 1️⃣ Isolate unreliable steps to safely retry 2️⃣ Model LLMs as ~idempotent & ~deterministic 3️⃣ Run asynchronously, subscribe to results Bonus: I made a thing

Thoughts on about safety & reliability in the age of agentic flows and durable workflows: Details in 🧵, but tl;dr: 1️⃣ Isolate unreliable steps to safely retry 2️⃣ Model LLMs as ~idempotent & ~deterministic 3️⃣ Run asynchronously, subscribe to results Bonus: I made a thing

View full thread

2️⃣ Treat LLM calls as ~idempotent, so long as you don't update memory/ DB state in the same step (see 1️⃣). To do multiple LLM calls in a step, you can journal (cache) LLM outputs (on success) so retrying a multi-prompt flow will be ~deterministic.

A nice feature of a platform like Convex is that if your side-effect is just writing to the DB, you can just throw it in a mutation and not worry about journaling yourself - it'll be transactional, with auto-retries on conflicts

Thoughts on about safety & reliability in the age of agentic flows and durable workflows: Details in 🧵, but tl;dr: 1️⃣ Isolate unreliable steps to safely retry 2️⃣ Model LLMs as ~idempotent & ~deterministic 3️⃣ Run asynchronously, subscribe to results Bonus: I made a thing

View full thread

3️⃣ Kick off workflows asynchronously vs. running them in an HTTP handler. If the user's network blips or they refresh the page, the workflow is in limbo. Persist / return the run identifier instead. Subscriptions also enable multiple users to observe results.

I made a Convex Component that makes this easy: Workpool (﹫convex-dev/workpool) - Configure retries - Limit overall parallelism - Guaranteed to run to completion (durable and transactional onComplete handler) I'm now using it inside of a Mastra Component (ships tomorrow?)

Thoughts on about safety & reliability in the age of agentic flows and durable workflows: Details in 🧵, but tl;dr: 1️⃣ Isolate unreliable steps to safely retry 2️⃣ Model LLMs as ~idempotent & ~deterministic 3️⃣ Run asynchronously, subscribe to results Bonus: I made a thing

1️⃣ Isolate unreliable steps (LLM APIs atm) from steps with side-effects (persistence, POST requests, etc). This allows safely retrying them. 90% success -> 99.9% success (w/ 3 retries) (best practice: add backoff and jitter) My Workpool component

I'm working on handling [agentic] workflows in @convex.dev using Mastra to define them. Anyone interested in an alpha I made over the last week? Based on the architecture diagram I shared. It uses my durable Workpool component to execute them with parallelism limits, retries, etc. (Open Source)

So fun to work with Annie. She helped a ton, giving us some just-in-time feedback on self-hosting ergonomics. Really grateful that we met through @kentcdodds.com's #EpicWebCamp.

Annie Sexton@anniesexton.com

Convex is one of the coolest databases I've ever used, and now you can host one yourself! So obviously I had to try it out on Fly.io (spoiler, it was awesome) www.youtube.com/watch?v=YPCg...

Self-hosting Convex just got way easier, including running the dashboard. npx degit get-convex/convex-backend/self-hosted/fly fly && cd fly/backend && fly launch Dockerfiles, images, binaries @flydotio and more. Check it out 🧵

A really cool thing here is that to push new code, you don't have to re-build or re-deploy an image. Just like Convex's cloud hosting, your app keeps running during code deploys and atomically changes over code and schema validation. You're self-hosting the platform 🤯

I used AI to write a script to help me write AI evals to help AI write Convex code. Very meta 💡Each step uses RAG from previous evals Flow: 1. 🧑‍💻oneliner -> 🤖 task description -> 🧑‍💻 audit 2. 🤖 answer ->🧑‍💻 audit 3. 🤖 tests for answer ->🧑‍💻 audit 🔗 to evals 👀 & my script in 🧵

github.comGitHub - get-convex/convex-evalsContribute to get-convex/convex-evals development by creating an account on GitHub.