-
-
Notifications
You must be signed in to change notification settings - Fork 874
feat: add Valkey-backed TaskStore and ResumableStreamStore #1259
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
rileydes-improving
wants to merge
22
commits into
VoltAgent:main
Choose a base branch
from
rileydes-improving:feat/valkey-store-providers
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 15 commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
c13aa00
chore(.gitignore): add kiro directory to gitignore
rileydes-improving 59032a0
feat(a2a-server): add Valkey task store implementation
rileydes-improving 9dff21f
test(a2a-server): add comprehensive tests for ValkeyTaskStore
rileydes-improving 1720d40
feat(resumable-streams): add Valkey store implementation
rileydes-improving 75d003c
test(resumable-streams): add comprehensive tests for ValkeyStore
rileydes-improving 70c021a
feat(examples): add Valkey store example with A2A and
rileydes-improving cbee145
fix(a2a-server): address review findings for ValkeyTaskStore
rileydes-improving 6a0d202
fix(resumable-streams): address eview findings for Valkey store
rileydes-improving d625eba
docs(examples): update changeset and example config for Valkey store PR
rileydes-improving 66147db
fix(resumable-streams): correct publish arg order and subscription ha…
rileydes-improving ec417c0
fix(a2a-server): add TTL validation and key escaping to ValkeyTaskStore
rileydes-improving 5081830
fix(changeset): bump @voltagent/a2a-server to major for breaking change
rileydes-improving c68b0e1
refactor(a2a-server): use structured logger and narrow valkey exports
rileydes-improving a3675e2
fix(examples): fix tsconfig, port validation, and README lint
rileydes-improving d418794
refactor(a2a-server): improve Zod schema integration and test coverage
rileydes-improving ccb3a12
refactor(resumable-streams,a2a-server): move Valkey exports to sub-pa…
rileydes-improving d0ae0e9
fix(examples): update with-valkey-store imports for sub-path exports
rileydes-improving a80210c
fix(a2a-server): normalize task ID in ValkeyTaskStore.save to prevent…
rileydes-improving ff0af2d
fix(a2a-server): detect safeStringify error sentinel before persistin…
rileydes-improving 0fca0dd
docs(a2a-server,resumable-streams): add JSDoc comments to schemas, ty…
rileydes-improving ee37043
fix(a2a-server): harden error handling, validation, and serialization
rileydes-improving 49941b1
fix(a2a-server): add error handling for task failure persistence
rileydes-improving File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| --- | ||
| "@voltagent/a2a-server": major | ||
| "@voltagent/resumable-streams": minor | ||
| --- | ||
|
|
||
| feat: add Valkey-backed TaskStore and ResumableStreamStore providers | ||
|
|
||
| Adds `ValkeyTaskStore` to `@voltagent/a2a-server` and `createResumableStreamValkeyStore` to | ||
| `@voltagent/resumable-streams`, enabling distributed persistence via the `@valkey/valkey-glide` | ||
| client library. Both stores support configurable key prefixes, optional TTL-based expiration, and | ||
| standalone or cluster Valkey deployments. The `@valkey/valkey-glide` peer dependency is optional so | ||
| consumers who don't use Valkey are unaffected. | ||
|
|
||
| **Breaking change in `@voltagent/a2a-server`:** `A2AServerConfig` now accepts an optional | ||
| `taskStore` property. When provided, it takes precedence over the `deps.taskStore` argument passed | ||
| to `A2AServer.initialize()`. The full precedence chain is: | ||
| `config.taskStore` > `deps.taskStore` > `InMemoryTaskStore`. A debug-level log is emitted when | ||
| `config.taskStore` overrides a non-null `deps.taskStore`. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| OPENAI_API_KEY= | ||
|
|
||
| # Valkey connection (defaults shown) | ||
| VALKEY_HOST=localhost | ||
| VALKEY_PORT=6379 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| node_modules | ||
| dist | ||
| .env |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,170 @@ | ||
| <div align="center"> | ||
| <a href="https://voltagent.dev/"> | ||
| <img width="1800" alt="VoltAgent banner" src="https://github.com/user-attachments/assets/452a03e7-eeda-4394-9ee7-0ffbcf37245c" /> | ||
| </a> | ||
|
|
||
| <br /> | ||
| <br /> | ||
|
|
||
| <div align="center"> | ||
| <a href="https://voltagent.dev">Home Page</a> | | ||
| <a href="https://voltagent.dev/docs/">Documentation</a> | | ||
| <a href="https://github.com/voltagent/voltagent/tree/main/examples">Examples</a> | | ||
| <a href="https://s.voltagent.dev/discord">Discord</a> | | ||
| <a href="https://voltagent.dev/blog/">Blog</a> | ||
| </div> | ||
| </div> | ||
|
|
||
| <br /> | ||
|
|
||
| <div align="center"> | ||
| <strong>VoltAgent is an open source TypeScript framework for building and orchestrating AI agents.</strong><br /> | ||
| Escape the limitations of no-code builders and the complexity of starting from scratch. | ||
| <br /> | ||
| <br /> | ||
| </div> | ||
|
|
||
| <div align="center"> | ||
|
|
||
| [](https://www.npmjs.com/package/@voltagent/core) | ||
| [](../../CODE_OF_CONDUCT.md) | ||
| [](https://s.voltagent.dev/discord) | ||
| [](https://twitter.com/voltagent_dev) | ||
|
|
||
| </div> | ||
|
|
||
| <br /> | ||
|
|
||
| # VoltAgent with Valkey Store Example | ||
|
|
||
| This example demonstrates how to use **Valkey** as a distributed backing store for both A2A task persistence and resumable streaming in VoltAgent. It uses the `@valkey/valkey-glide` client library for high-performance access to Valkey (standalone or cluster). | ||
|
|
||
| ## What you get | ||
|
|
||
| - **ValkeyTaskStore** — Persists A2A task records to Valkey with configurable key prefixes and TTL-based expiration. | ||
| - **ValkeyResumableStreamStore** — Manages resumable streaming sessions via Valkey pub/sub and key-value operations. | ||
| - A minimal VoltAgent project with a `SupportAgent` exposed over the A2A protocol, backed entirely by Valkey. | ||
|
|
||
| ## Structure | ||
|
|
||
| ```text | ||
| examples/with-valkey-store | ||
| ├── src/ | ||
| │ ├── agents/assistant.ts # Example agent definition | ||
| │ └── index.ts # VoltAgent bootstrap with Valkey stores | ||
| ├── .env.example # Environment variable template | ||
| ├── package.json | ||
| ├── tsconfig.json | ||
| └── README.md | ||
| ``` | ||
|
|
||
| ## Prerequisites | ||
|
|
||
| - Node.js 20+ | ||
| - `pnpm` | ||
| - A running Valkey instance (or Redis-compatible server) | ||
| - `OPENAI_API_KEY` in your environment | ||
|
|
||
| ### Start Valkey locally with Docker | ||
|
|
||
| ```bash | ||
| docker run -d --name valkey -p 6379:6379 valkey/valkey:8 | ||
| ``` | ||
|
|
||
| ## Run locally | ||
|
|
||
| 1. Copy the environment template and fill in your keys: | ||
|
|
||
| ```bash | ||
| cp .env.example .env | ||
| ``` | ||
|
|
||
| 2. Install dependencies and start the dev server: | ||
|
|
||
| ```bash | ||
| pnpm install | ||
| pnpm --filter voltagent-example-with-valkey-store dev | ||
| ``` | ||
|
|
||
| The server listens on `http://localhost:3141`. | ||
|
|
||
| ## Configuration | ||
|
|
||
| Environment variables: | ||
|
|
||
| | Variable | Default | Description | | ||
| | ---------------- | ----------- | ------------------------------------ | | ||
| | `OPENAI_API_KEY` | — | OpenAI API key for the example agent | | ||
| | `VALKEY_HOST` | `localhost` | Valkey server hostname | | ||
| | `VALKEY_PORT` | `6379` | Valkey server port | | ||
|
|
||
| ### Key prefixes and TTL | ||
|
|
||
| Both stores accept `keyPrefix` and `ttlSeconds` options: | ||
|
|
||
| ```typescript | ||
| // Task store — keys like "my-tasks:agentId::taskId" | ||
| const taskStore = await createValkeyTaskStore({ | ||
| client: valkeyClient, | ||
| keyPrefix: "my-tasks", | ||
| ttlSeconds: 3600, | ||
| }); | ||
|
|
||
| // Stream store — keys like "my-streams:active:userId-conversationId" | ||
| const streamStore = await createResumableStreamValkeyStore({ | ||
| client: valkeyClient, | ||
| clientConfig: { addresses: [{ host: "localhost", port: 6379 }] }, | ||
| keyPrefix: "my-streams", | ||
| ttlSeconds: 600, | ||
| }); | ||
| ``` | ||
|
|
||
| ### Cluster mode | ||
|
|
||
| Both stores accept `GlideClient` or `GlideClusterClient`: | ||
|
|
||
| ```typescript | ||
| import { GlideClusterClient } from "@valkey/valkey-glide"; | ||
|
|
||
| const clusterClient = await GlideClusterClient.createClient({ | ||
| addresses: [ | ||
| { host: "node1.example.com", port: 6379 }, | ||
| { host: "node2.example.com", port: 6379 }, | ||
| ], | ||
| useTLS: true, | ||
| }); | ||
|
|
||
| const taskStore = new ValkeyTaskStore({ client: clusterClient }); | ||
| ``` | ||
|
|
||
| ## Try it | ||
|
|
||
| ```bash | ||
| # Fetch the agent card | ||
| curl http://localhost:3141/.well-known/supportagent/agent-card.json | jq | ||
|
|
||
| # Send a message | ||
| curl -X POST http://localhost:3141/a2a/supportagent \ | ||
| -H "Content-Type: application/json" \ | ||
| -d '{ | ||
| "jsonrpc": "2.0", | ||
| "id": "1", | ||
| "method": "message/send", | ||
| "params": { | ||
| "message": { | ||
| "kind": "message", | ||
| "role": "user", | ||
| "messageId": "msg-1", | ||
| "parts": [{ "kind": "text", "text": "What time is it?" }] | ||
| } | ||
| } | ||
| }' | ||
| ``` | ||
|
|
||
| ## Next steps | ||
|
|
||
| - Adjust `ttlSeconds` to match your retention requirements (or omit it for no expiration). | ||
| - Use `GlideClusterClient` for production Valkey cluster deployments (e.g., AWS ElastiCache Valkey). | ||
| - Add TLS by setting `useTLS: true` in the client configuration. | ||
|
|
||
| Happy hacking! 🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| { | ||
| "name": "voltagent-example-with-valkey-store", | ||
| "version": "0.0.0", | ||
| "dependencies": { | ||
| "@valkey/valkey-glide": "^2.3.1", | ||
| "@voltagent/a2a-server": "^2.0.3", | ||
| "@voltagent/core": "^2.7.2", | ||
| "@voltagent/internal": "^1.0.3", | ||
| "@voltagent/logger": "^2.0.2", | ||
| "@voltagent/resumable-streams": "^2.0.2", | ||
| "@voltagent/server-hono": "^2.0.12", | ||
| "ai": "^6.0.0", | ||
| "zod": "^3.25.76" | ||
| }, | ||
| "devDependencies": { | ||
| "@types/node": "^24.2.1", | ||
| "tsx": "^4.21.0", | ||
| "typescript": "^5.8.2" | ||
| }, | ||
| "private": true, | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "https://github.com/VoltAgent/voltagent.git", | ||
| "directory": "examples/with-valkey-store" | ||
| }, | ||
| "scripts": { | ||
| "build": "tsc", | ||
| "dev": "tsx watch --env-file=.env ./src", | ||
| "start": "node dist/index.js" | ||
| }, | ||
| "type": "module" | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| import { Agent, createTool } from "@voltagent/core"; | ||
| import { z } from "zod"; | ||
|
|
||
| const statusTool = createTool({ | ||
| name: "status", | ||
| description: "Return the current time in ISO format", | ||
| parameters: z.object({}), | ||
| async execute() { | ||
| return { | ||
| timestamp: new Date().toISOString(), | ||
| }; | ||
| }, | ||
| }); | ||
|
|
||
| export const assistant = new Agent({ | ||
| id: "supportagent", | ||
| name: "SupportAgent", | ||
| instructions: "Reply with helpful answers and include the current time when relevant.", | ||
| model: "openai/gpt-4o-mini", | ||
| tools: [statusTool], | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| import { GlideClient } from "@valkey/valkey-glide"; | ||
| import { A2AServer, createValkeyTaskStore } from "@voltagent/a2a-server"; | ||
| import { VoltAgent } from "@voltagent/core"; | ||
| import { createPinoLogger } from "@voltagent/logger"; | ||
| import { | ||
| createResumableStreamAdapter, | ||
| createResumableStreamValkeyStore, | ||
| } from "@voltagent/resumable-streams"; | ||
| import { honoServer } from "@voltagent/server-hono"; | ||
| import { assistant } from "./agents/assistant"; | ||
|
|
||
| const logger = createPinoLogger({ | ||
| name: "with-valkey-store", | ||
| level: "debug", | ||
| }); | ||
|
|
||
| const host = process.env.VALKEY_HOST ?? "localhost"; | ||
| const rawPort = process.env.VALKEY_PORT; | ||
| const port = rawPort !== undefined ? Number(rawPort) : 6379; | ||
| if (!Number.isInteger(port) || port < 1 || port > 65535) { | ||
| throw new Error(`Invalid VALKEY_PORT "${rawPort}": must be an integer between 1 and 65535`); | ||
| } | ||
|
|
||
| /** | ||
| * Bootstraps a VoltAgent instance backed by Valkey for both A2A task | ||
| * persistence and resumable streaming. Connects to the Valkey server | ||
| * specified by `VALKEY_HOST` / `VALKEY_PORT` environment variables | ||
| * (defaulting to `localhost:6379`), then starts an HTTP server on port 3141. | ||
| */ | ||
| async function main() { | ||
| const valkeyClient = await GlideClient.createClient({ | ||
| addresses: [{ host, port }], | ||
| }); | ||
| logger.info(`Connected to Valkey at ${host}:${port}`); | ||
|
|
||
| const taskStore = await createValkeyTaskStore({ | ||
| client: valkeyClient, | ||
| keyPrefix: "example-tasks", | ||
| ttlSeconds: 3600, | ||
| }); | ||
|
|
||
| const streamStore = await createResumableStreamValkeyStore({ | ||
| client: valkeyClient, | ||
| clientConfig: { addresses: [{ host, port }] }, | ||
| keyPrefix: "example-streams", | ||
| ttlSeconds: 600, | ||
| }); | ||
|
|
||
| const streamAdapter = await createResumableStreamAdapter({ | ||
| streamStore, | ||
| }); | ||
|
|
||
| const a2aServerFactory = () => | ||
| new A2AServer({ | ||
| name: "SupportAgent", | ||
| version: "0.1.0", | ||
| description: "A2A server with Valkey-backed task and stream persistence", | ||
| taskStore, | ||
| }); | ||
|
|
||
| new VoltAgent({ | ||
| agents: { assistant }, | ||
| a2aServers: { supportAgent: a2aServerFactory }, | ||
| server: honoServer({ | ||
| port: 3141, | ||
| resumableStream: { adapter: streamAdapter }, | ||
| }), | ||
| logger, | ||
| }); | ||
|
|
||
| logger.info("VoltAgent with Valkey stores running on http://localhost:3141"); | ||
| } | ||
|
|
||
| main().catch((err) => { | ||
| logger.error("Failed to start", { error: err }); | ||
| process.exit(1); | ||
| }); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| { | ||
| "extends": "../../tsconfig.json", | ||
| "compilerOptions": { | ||
| "rootDir": "src", | ||
| "outDir": "dist", | ||
| "module": "NodeNext", | ||
| "moduleResolution": "NodeNext", | ||
| "target": "ES2022", | ||
| "types": ["node"], | ||
| "esModuleInterop": true | ||
| }, | ||
| "include": ["src/**/*"], | ||
| "exclude": ["dist"] | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.