Skip to content
Open
Show file tree
Hide file tree
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 Apr 27, 2026
59032a0
feat(a2a-server): add Valkey task store implementation
rileydes-improving Apr 30, 2026
9dff21f
test(a2a-server): add comprehensive tests for ValkeyTaskStore
rileydes-improving Apr 30, 2026
1720d40
feat(resumable-streams): add Valkey store implementation
rileydes-improving Apr 30, 2026
75d003c
test(resumable-streams): add comprehensive tests for ValkeyStore
rileydes-improving Apr 30, 2026
70c021a
feat(examples): add Valkey store example with A2A and
rileydes-improving Apr 30, 2026
cbee145
fix(a2a-server): address review findings for ValkeyTaskStore
rileydes-improving Apr 30, 2026
6a0d202
fix(resumable-streams): address eview findings for Valkey store
rileydes-improving Apr 30, 2026
d625eba
docs(examples): update changeset and example config for Valkey store PR
rileydes-improving Apr 30, 2026
66147db
fix(resumable-streams): correct publish arg order and subscription ha…
rileydes-improving May 1, 2026
ec417c0
fix(a2a-server): add TTL validation and key escaping to ValkeyTaskStore
rileydes-improving May 1, 2026
5081830
fix(changeset): bump @voltagent/a2a-server to major for breaking change
rileydes-improving May 1, 2026
c68b0e1
refactor(a2a-server): use structured logger and narrow valkey exports
rileydes-improving May 1, 2026
a3675e2
fix(examples): fix tsconfig, port validation, and README lint
rileydes-improving May 1, 2026
d418794
refactor(a2a-server): improve Zod schema integration and test coverage
rileydes-improving May 1, 2026
ccb3a12
refactor(resumable-streams,a2a-server): move Valkey exports to sub-pa…
rileydes-improving May 1, 2026
d0ae0e9
fix(examples): update with-valkey-store imports for sub-path exports
rileydes-improving May 1, 2026
a80210c
fix(a2a-server): normalize task ID in ValkeyTaskStore.save to prevent…
rileydes-improving May 1, 2026
ff0af2d
fix(a2a-server): detect safeStringify error sentinel before persistin…
rileydes-improving May 1, 2026
0fca0dd
docs(a2a-server,resumable-streams): add JSDoc comments to schemas, ty…
rileydes-improving May 4, 2026
ee37043
fix(a2a-server): harden error handling, validation, and serialization
rileydes-improving May 4, 2026
49941b1
fix(a2a-server): add error handling for task failure persistence
rileydes-improving May 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .changeset/add-valkey-store-providers.md
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`.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,9 @@ skills
!packages/core/src/workspace/skills
!packages/core/src/workspace/skills/**

# kiro
.kiro

dev-debug.log
node_modules/

Expand Down
5 changes: 5 additions & 0 deletions examples/with-valkey-store/.env.example
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
3 changes: 3 additions & 0 deletions examples/with-valkey-store/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules
dist
.env
170 changes: 170 additions & 0 deletions examples/with-valkey-store/README.md
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">

[![npm version](https://img.shields.io/npm/v/@voltagent/core.svg)](https://www.npmjs.com/package/@voltagent/core)
[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.0-4baaaa.svg)](../../CODE_OF_CONDUCT.md)
[![Discord](https://img.shields.io/discord/1361559153780195478.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://s.voltagent.dev/discord)
[![Twitter Follow](https://img.shields.io/twitter/follow/voltagent_dev?style=social)](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! 🚀
32 changes: 32 additions & 0 deletions examples/with-valkey-store/package.json
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"
}
21 changes: 21 additions & 0 deletions examples/with-valkey-store/src/agents/assistant.ts
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],
});
77 changes: 77 additions & 0 deletions examples/with-valkey-store/src/index.ts
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 }],
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.
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);
});
14 changes: 14 additions & 0 deletions examples/with-valkey-store/tsconfig.json
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"]
}
6 changes: 6 additions & 0 deletions packages/a2a-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,14 @@
"main": "dist/index.js",
"module": "dist/index.mjs",
"peerDependencies": {
"@valkey/valkey-glide": ">=2.3.1",
"@voltagent/core": "^2.0.0"
},
"peerDependenciesMeta": {
"@valkey/valkey-glide": {
"optional": true
}
},
"repository": {
"type": "git",
"url": "https://github.com/VoltAgent/voltagent.git",
Expand Down
2 changes: 2 additions & 0 deletions packages/a2a-server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ export * from "./types";
export * from "./server";
export * from "./protocol";
export * from "./store";
export { createValkeyTaskStore, ValkeyTaskStore } from "./valkey-store";
export type { ValkeyTaskStoreOptions } from "./valkey-store";
export * from "./tasks";
export * from "./adapters/agent";
export * from "./adapters/message";
Expand Down
Loading