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.
@ianmacartney.bsky.social
Friendly engineer at Convex.dev
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.
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
03.09.2025 06:47 β π 1 π 0 π¬ 0 π 0I'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
A recent talk on developing the Agent Component:
www.youtube.com/watch?v=YM9n...
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
Example:
github.com/get-convex/a...
Agent component:
convex.dev/components/a...
Rate Limiter:
www.convex.dev/components/r...
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)
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?
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
12.06.2025 06:12 β π 1 π 0 π¬ 1 π 0Yeah 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...
Agent component: convex.dev/components/a...
YT talk: youtube.com/watch?v=3Ydg...
Durable workflow article: stack.convex.dev/durable-work...
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 πΊπ->π§΅
wdyt?
const messages = useThreadMessages(
apiβ.foo.listMessages,
{ threadId },
{ initialNumItems: 10, stream: true },
);
const sendMessage = useMutation(
apiβ.foo.generateText,
).withOptimisticUpdate(
optimisticallySendMessage(apiβ.foo.listMessages),
);
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
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
βSimple Made Easyβ is incredibly relevant nowadays where βeasyβ but not βsimpleβ systems abound
youtu.be/SxdOUGdseq4?...
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
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 π§΅
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π
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: π§΅
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 π§΅
Agent component: convex.dev/components/a...
Article: stack.convex.dev/ai-agents
Code:
github.com/get-convex/a...
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() ?? [];
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.
I pushed a fix this morning, so if you already installed it, upgrade to `@convex-dev/agent@latest`
09.04.2025 23:32 β π 1 π 0 π¬ 0 π 0I 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...
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...
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.
π§΅
Hopefully you can avoid making my mistakes:
stack.convex.dev/reimplementi...
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 π§΅
@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
28.03.2025 03:03 β π 2 π 0 π¬ 1 π 0