-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
25
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 all commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
502d643
chore(.gitignore): add kiro directory to gitignore
rileydes-improving d8a7b45
feat(a2a-server): add Valkey task store implementation
rileydes-improving 89e20e3
test(a2a-server): add comprehensive tests for ValkeyTaskStore
rileydes-improving 52127e0
feat(resumable-streams): add Valkey store implementation
rileydes-improving 1b8c29f
test(resumable-streams): add comprehensive tests for ValkeyStore
rileydes-improving 9ab999c
feat(examples): add Valkey store example with A2A and
rileydes-improving 06b712f
fix(a2a-server): address review findings for ValkeyTaskStore
rileydes-improving 43ea093
fix(resumable-streams): address eview findings for Valkey store
rileydes-improving 8fea19e
docs(examples): update changeset and example config for Valkey store PR
rileydes-improving d19c6a8
fix(resumable-streams): correct publish arg order and subscription ha…
rileydes-improving dded73d
fix(a2a-server): add TTL validation and key escaping to ValkeyTaskStore
rileydes-improving 6fd2e0a
fix(changeset): bump @voltagent/a2a-server to major for breaking change
rileydes-improving dc8487f
refactor(a2a-server): use structured logger and narrow valkey exports
rileydes-improving b48a477
fix(examples): fix tsconfig, port validation, and README lint
rileydes-improving f26d424
refactor(a2a-server): improve Zod schema integration and test coverage
rileydes-improving 91a8b08
refactor(resumable-streams,a2a-server): move Valkey exports to sub-pa…
rileydes-improving 8989c4f
fix(examples): update with-valkey-store imports for sub-path exports
rileydes-improving 31badfa
fix(a2a-server): normalize task ID in ValkeyTaskStore.save to prevent…
rileydes-improving 4c748fd
fix(a2a-server): detect safeStringify error sentinel before persistin…
rileydes-improving 252855b
docs(a2a-server,resumable-streams): add JSDoc comments to schemas, ty…
rileydes-improving 7e6ea03
fix(a2a-server): harden error handling, validation, and serialization
rileydes-improving bcab4bf
fix(a2a-server): add error handling for task failure persistence
rileydes-improving b1edfd5
feat: add CLIENT SETNAME to all Valkey connections in example
rileydes-improving 3b63ca2
chore: update lockfile
rileydes-improving 07829bb
Merge remote-tracking branch 'origin/main' into feat/valkey-store-pro…
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,22 @@ | ||
| 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(), | ||
| }; | ||
| }, | ||
| }); | ||
|
|
||
| /** Pre-configured support agent with a `status` tool that returns the current time. */ | ||
| 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 } from "@voltagent/a2a-server"; | ||
| import { createValkeyTaskStore } from "@voltagent/a2a-server/valkey-store"; | ||
| import { VoltAgent } from "@voltagent/core"; | ||
| import { createPinoLogger } from "@voltagent/logger"; | ||
| import { createResumableStreamAdapter } from "@voltagent/resumable-streams"; | ||
| import { createResumableStreamValkeyStore } from "@voltagent/resumable-streams/valkey-store"; | ||
| import { honoServer } from "@voltagent/server-hono"; | ||
| import { assistant } from "./agents/assistant.js"; | ||
|
|
||
| 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 }], | ||
| clientName: "voltagent_a2a_store_client", | ||
| }); | ||
| 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 }], clientName: "voltagent_a2a_stream_client" }, | ||
| 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
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.