Sign in

Ian Macartney

@ianmacartney.bsky.social
74 followers 24 following 61 posts

Friendly engineer at Convex.dev

PostsRepliesMedia
Ian Macartney @ianmacartney.bsky.social · 24/09/2025
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.
000
Ian Macartney @ianmacartney.bsky.social · 03/09/2025
Love to hear it! Hop into the agents channel in the Convex Discord if you aren't already - that's where I give updates & folks give feedback to help shape the future of it
010
Ian Macartney @ianmacartney.bsky.social · 08/08/2025
I've added new features to my middleware-esque helper library, and got inspired to write down everything I think about authorization: stack.convex.dev/authorization
stack.convex.dev
Authorization Best Practices and Implementation Guide
Learn about authorization techniques and how to implemented them with practical examples.
000
Ian Macartney @ianmacartney.bsky.social · 08/08/2025
A recent talk on developing the Agent Component: www.youtube.com/watch?v=YM9n...
youtube.com
How to Build Realtime AI Agents with Convex Components
YouTube video by Convex
000
Ian Macartney @ianmacartney.bsky.social · 22/07/2025
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
000
Ian Macartney @ianmacartney.bsky.social · 17/06/2025
Example: github.com/get-convex/a... Agent component: convex.dev/components/a... Rate Limiter: www.convex.dev/components/r...
github.com
000
Ian Macartney @ianmacartney.bsky.social · 17/06/2025
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)
100
Ian Macartney @ianmacartney.bsky.social · 13/06/2025
That sounds compelling, and yeah system prompts do feel like a pretty poor bound. Now that I think of it I agree it's pretty aligned. What's your preferred way to handle routing / dispatching / step-by-step tool calls btw?
210
Ian Macartney @ianmacartney.bsky.social · 12/06/2025
but I may be over-indexing on the string-based signature. Definitely on my list of things to dig deeper on. Like most things you end up having more in common than you expect from the outset
110
Ian Macartney @ianmacartney.bsky.social · 12/06/2025
Yeah DSPy came on my radar recently. It seems interesting & audacious! My gut feel is that code is still king though - the second you want more flexibility, composability, or control over context / prompting, you'd be fighting the framework. A la #4 here stack.convex.dev/ai-agents#go...
stack.convex.dev
AI Agents with Built-in Memory
With this new backend component, augment Agents to automatically save and search message history per-thread, providing realtime results across multipl...
010
Ian Macartney @ianmacartney.bsky.social · 10/06/2025
Agent component: convex.dev/components/a... YT talk: youtube.com/watch?v=3Ydg... Durable workflow article: stack.convex.dev/durable-work...
convex.dev
AI Agent
Agents organize your AI workflows into units, with message history and vector search built in.
000
Ian Macartney @ianmacartney.bsky.social · 10/06/2025
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 📺🔗->🧵
200
Ian Macartney @ianmacartney.bsky.social · 31/05/2025
wdyt? const messages = useThreadMessages( api​.foo.listMessages, { threadId }, { initialNumItems: 10, stream: true }, ); const sendMessage = useMutation( api​.foo.generateText, ).withOptimisticUpdate( optimisticallySendMessage(api​.foo.listMessages), );
000
Ian Macartney @ianmacartney.bsky.social · 31/05/2025
Agent Component: convex.dev/components/a... Example code: github.com/get-convex/a... Changelog: github.com/get-convex/a... It one-ups persistent-text-streaming by syncing down only the deltas, not the full text, so you don't pay for bandwidth except proportional to the total length
convex.dev
AI Agent
Agents organize your AI workflows into units, with message history and vector search built in.
100
Ian Macartney @ianmacartney.bsky.social · 31/05/2025
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
100
Ian Macartney @ianmacartney.bsky.social · 31/05/2025
“Simple Made Easy” is incredibly relevant nowadays where “easy” but not “simple” systems abound youtu.be/SxdOUGdseq4?...
youtu.be
"Simple Made Easy" - Rich Hickey (2011)
YouTube video by Strange Loop Conference
000
Ian Macartney @ianmacartney.bsky.social · 23/05/2025
Hosted playground: get-convex.github.io/agent/ Agent component / framework: github.com/get-convex/a... Playground directory: github.com/get-convex/a... Fun fact: It can target your @convex.dev backend if you if you expose the API, using API key auth. Statically hosted on GitHub pages
get-convex.github.io
Convex Agent Playground
Evaluate and test agents using @convex-dev/agent
010
Ian Macartney @ianmacartney.bsky.social · 23/05/2025
Agent Playground for @​convex-dev/agent is live! Investigate threads, messages, tool calls Dial in context params Iterate on prompting, etc. For the @​convex-dev/agent component. Links in 🧵
100
Ian Macartney @ianmacartney.bsky.social · 10/04/2025
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🎉
000
Ian Macartney @ianmacartney.bsky.social · 10/04/2025
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: 🧵
100
Ian Macartney @ianmacartney.bsky.social · 10/04/2025
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 🧵
100
Ian Macartney @ianmacartney.bsky.social · 10/04/2025
Agent component: convex.dev/components/a... Article: stack.convex.dev/ai-agents Code: github.com/get-convex/a...
convex.dev
AI Agent
Agents organize your AI workflows into units, with message history and vector search built in.
000
Ian Macartney @ianmacartney.bsky.social · 10/04/2025
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.
    let textSearchMessages: Doc<"messages">[] | undefined;
    if (args.text) {
      textSearchMessages = await ctx.runQuery(api.messages.textSearch, {
        userId: args.userId,
        threadId: args.threadId,
        text: args.text,
        limit,
      });
    }
    if (args.vector) {
      const dimension = args.vector.length as VectorDimension;
      if (!VectorDimensions.includes(dimension)) {
        throw new Error(`Unsupported vector dimension: ${dimension}`);
      }
      const vectors = (
        await searchVectors(ctx, args.vector, {
          dimension,
          model: args.vectorModel ?? "unknown",
          table: "messages",
          userId: args.userId,
          threadId: args.threadId,
          limit,
        })
      ).filter((v) => v._score > (args.vectorScoreThreshold ?? 0));
      // Reciprocal rank fusion
      const k = 10;
      const textEmbeddingIds = textSearchMessages?.map((m) => m.embeddingId);
      const vectorScores = vectors
        .map((v, i) => ({
          id: v._id,
          score:
            1 / (i + k) +
            1 / ((textEmbeddingIds?.indexOf(v._id) ?? Infinity) + k),
        }))
        .sort((a, b) => b.score - a.score);
      const vectorIds = vectorScores.slice(0, limit).map((v) => v.id);
      const messages: Doc<"messages">[] = await ctx.runQuery(
        internal.messages._fetchVectorMessages,
        {
          userId: args.userId,
          threadId: args.threadId,
          vectorIds,
          textSearchMessages: textSearchMessages?.filter(
            (m) => !vectorIds.includes(m.embeddingId!)
          ),
          messageRange: args.messageRange ?? DEFAULT_MESSAGE_RANGE,
          parentMessageId: args.parentMessageId,
          limit,
        }
      );
      return messages;
    }
    return textSearchMessages?.flat() ?? [];
100
Ian Macartney @ianmacartney.bsky.social · 10/04/2025
I pushed a fix this morning, so if you already installed it, upgrade to `@convex-dev/agent@latest`
010
Ian Macartney @ianmacartney.bsky.social · 08/04/2025
I launched this today! Adding memory to AI SDK Agents with tools and RAG. Article on Agentic Workflow: stack.convex.dev/ai-agents Agent framework: convex.dev/components/a...
stack.convex.dev
AI Agents with Built-in Memory
With this new backend component, augment Agents to automatically save and search message history per-thread, providing realtime results across multipl...
000
Ian Macartney @ianmacartney.bsky.social · 08/04/2025
Run workflows reliably and asynchronously, using Inngest-style code. Why you need Durable Workflows for agentic systems: stack.convex.dev/durable-work... Workflow component: convex.dev/components/w...
stack.convex.dev
Agents Need Durable Workflows and Strong Guarantees
Agents rely on long-lived workflows, but when happens when they fail midway through? Here are the tools you need to manage correctness and reliability...
100
Ian Macartney @ianmacartney.bsky.social · 08/04/2025
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. 🧵
210
Ian Macartney @ianmacartney.bsky.social · 31/03/2025
Hopefully you can avoid making my mistakes: stack.convex.dev/reimplementi...
stack.convex.dev
I reimplemented Mastra workflows and I regret it
I reimplemented Mastra’s agentic workflows with durable functions in Convex, and it was the wrong decision. Look at three common strategies (reimpleme...
000
Ian Macartney @ianmacartney.bsky.social · 31/03/2025
Welp not all experiments work out, but what will outlive all products is the insights you glean along the way. I reimplemented Mastra workflows in Convex last week and I regret it. Article in 🧵
100
Ian Macartney @ianmacartney.bsky.social · 28/03/2025
@anniesexton.com I may not have your skills, but I still had fun. One of these days I should graduate from the excalidraw center for kids who can't draw good and want to do other things good too
120
Ian Macartney @ianmacartney.bsky.social · 28/03/2025
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"?
Where's Ian? Oh, he's in a code hole. Should we tell him his idea is risky? No, I mean his head is literally in a hole coding. He's an ostrich, did you know that?
110
Ian Macartney @ianmacartney.bsky.social · 27/03/2025
Programmers know why this sorting is busted the way it is. Maybe they should also know better :P
000
Ian Macartney @ianmacartney.bsky.social · 26/03/2025
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.
000
Ian Macartney @ianmacartney.bsky.social · 25/03/2025
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
000
Ian Macartney @ianmacartney.bsky.social · 25/03/2025
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?)
000
Ian Macartney @ianmacartney.bsky.social · 25/03/2025
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.
100
Ian Macartney @ianmacartney.bsky.social · 25/03/2025
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.
200
Ian Macartney @ianmacartney.bsky.social · 25/03/2025
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
100
Ian Macartney @ianmacartney.bsky.social · 25/03/2025
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
100
Ian Macartney @ianmacartney.bsky.social · 25/03/2025
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)
010
Ian Macartney @ianmacartney.bsky.social · 22/03/2025
Made this architecture diagram yesterday, implementing it today. Any guesses on what it is? Wish me luck!
010
Ian Macartney @ianmacartney.bsky.social · 12/03/2025
but actually, will they be available sometime soon? Or up on the website? I'm excited to find out what happens next
100
Ian Macartney @ianmacartney.bsky.social · 11/03/2025
Tiago asked about integrating reactflow.dev with @convex.dev so I made a demo last night. - realtime syncing flow chart - authorize reads and writes - validation of custom data payloads - optimistic updates for UX 🧑‍🍳💋 I'm proud of it, and thanks Tiago for the help! github.com/ianmacartney...
000
Ian Macartney @ianmacartney.bsky.social · 11/03/2025
It'll even continue with retries if the server restarts midway through. And of course transactions will roll back & retry with optimistic concurrencty control / multi-version concurrency control per usual, since it's in @convex.dev
000
Ian Macartney @ianmacartney.bsky.social · 11/03/2025
YAAAAAAAAAAAAY! I want this. I want this. I want this. I want this. I want this. I want this. I want this. I want this.
110
Ian Macartney @ianmacartney.bsky.social · 11/03/2025
Tip: Use the `workpool` component to manage asynchronous workloads. - durable execution w/ onComplete handler - limit parallelism to be a nice neighbor - configure retries (w/ backoff, jitter, max attempts) - backlog & lag monitoring v0.2.1 out now www.convex.dev/components/w...
convex.dev
Workpool
Workpools give critical tasks priority by organizing async operations into separate, customizable queues.
110
Ian Macartney @ianmacartney.bsky.social · 20/02/2025
The Telepathy Tapes podcast is adding to the pile of worldview-changing things to grapple with. What deserves my attention?
At a crossroads, deciding between going deep on AI vs. ESP
000
Ian Macartney @ianmacartney.bsky.social · 14/02/2025
repo: github.com/mikecann/our... video: youtube.com/watch?v=cZMV... self-hosting docs: github.com/get-convex/c...
github.com
GitHub - mikecann/ourfiles: a simple way to share files on a private network
a simple way to share files on a private network. Contribute to mikecann/ourfiles development by creating an account on GitHub.
000
Ian Macartney @ianmacartney.bsky.social · 14/02/2025
Local file sharing app (Open Source) Drag your files onto the site Drag them off on another computer git clone github.com/mikecann/our... cd ourfiles/ docker compose up open localhost:5173 by @mikeysee video in 🧵
100
Ian Macartney @ianmacartney.bsky.social · 14/02/2025
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.
1112