From 5f15e58bf916f1ea8fe155ab2e95cbea21bcfa33 Mon Sep 17 00:00:00 2001 From: Zac Rosenbauer Date: Thu, 7 May 2026 22:50:43 -0400 Subject: [PATCH 1/4] chore: add agent instruction guidance --- .cursor/rules/agents.mdc | 6 - .gitignore | 18 +- AGENTS.md | 80 +++++--- contributing/README.md | 35 ++++ contributing/changesets.md | 41 +++++ contributing/coding-agents.md | 60 ++++++ contributing/linting.md | 38 ++++ {docs => contributing}/structure.md | 51 ++++-- {docs => contributing}/testing.md | 27 +-- {docs => contributing}/tooling.md | 34 +++- docs/linting.md | 32 ---- packages/core/AGENTS.md | 33 ++++ packages/core/CLAUDE.md | 1 + packages/core/src/agent/AGENTS.md | 25 +++ packages/core/src/agent/CLAUDE.md | 1 + packages/core/src/memory/AGENTS.md | 24 +++ packages/core/src/memory/CLAUDE.md | 1 + packages/core/src/voltops/AGENTS.md | 21 +++ packages/core/src/voltops/CLAUDE.md | 1 + packages/core/src/workflow/AGENTS.md | 21 +++ packages/core/src/workflow/CLAUDE.md | 1 + packages/core/src/workspace/AGENTS.md | 21 +++ packages/core/src/workspace/CLAUDE.md | 1 + scripts/sync-agent-instructions.mjs | 254 ++++++++++++++++++++++++++ 24 files changed, 729 insertions(+), 98 deletions(-) delete mode 100644 .cursor/rules/agents.mdc create mode 100644 contributing/README.md create mode 100644 contributing/changesets.md create mode 100644 contributing/coding-agents.md create mode 100644 contributing/linting.md rename {docs => contributing}/structure.md (53%) rename {docs => contributing}/testing.md (78%) rename {docs => contributing}/tooling.md (59%) delete mode 100644 docs/linting.md create mode 100644 packages/core/AGENTS.md create mode 120000 packages/core/CLAUDE.md create mode 100644 packages/core/src/agent/AGENTS.md create mode 120000 packages/core/src/agent/CLAUDE.md create mode 100644 packages/core/src/memory/AGENTS.md create mode 120000 packages/core/src/memory/CLAUDE.md create mode 100644 packages/core/src/voltops/AGENTS.md create mode 120000 packages/core/src/voltops/CLAUDE.md create mode 100644 packages/core/src/workflow/AGENTS.md create mode 120000 packages/core/src/workflow/CLAUDE.md create mode 100644 packages/core/src/workspace/AGENTS.md create mode 120000 packages/core/src/workspace/CLAUDE.md create mode 100755 scripts/sync-agent-instructions.mjs diff --git a/.cursor/rules/agents.mdc b/.cursor/rules/agents.mdc deleted file mode 100644 index 4b4b39086..000000000 --- a/.cursor/rules/agents.mdc +++ /dev/null @@ -1,6 +0,0 @@ ---- -description: -globs: -alwaysApply: true ---- -ALWAYS read the `AGENTS.md` in the root of the repository and apply the instructions to the current task. diff --git a/.gitignore b/.gitignore index fe0d10b5d..29ea612bc 100644 --- a/.gitignore +++ b/.gitignore @@ -147,11 +147,14 @@ packages/core/docs # Create VoltAgent App !packages/create-voltagent-app/templates/base/.env -# cursor -.cursor/* -!.cursor/rules/ -.cursor/rules/* -!.cursor/rules/agents.mdc +# Local coding-agent and IDE config directories/files. +# Keep shared instruction files such as AGENTS.md and CLAUDE.md tracked instead. +.agents/ +.claude +.codex/ +.cursor/ +.windsurf/ +/.mcp.json # vite and vitest vite.config.*.timestamp* @@ -163,8 +166,6 @@ vitest.config.*.timestamp* # vscode .vscode -# claude -.claude skills !packages/core/src/workspace/skills !packages/core/src/workspace/skills/** @@ -182,6 +183,9 @@ node_modules/ # serena .serena +# joggr +.joggr + # example skills !examples/with-workspace/workspace/skills diff --git a/AGENTS.md b/AGENTS.md index 537f98720..433d84d1b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,37 +1,73 @@ # VoltAgent -VoltAgent is an open-source TypeScript framework for building and orchestrating AI agents. +VoltAgent is an open-source TypeScript monorepo for building and orchestrating AI agents. -## What is VoltAgent? +## Repository Layout -VoltAgent is a comprehensive framework that enables developers to build sophisticated AI agents with memory, tools, observability, and sub-agent capabilities. It provides direct AI SDK integration with comprehensive OpenTelemetry tracing built-in. +- `packages/*` contains published packages. +- `packages/core` contains the core runtime for agents, workflows, memory, tools, observability, workspaces, and VoltOps integration. +- `examples/*` contains runnable example apps. Keep examples simple and copy-pasteable. +- `website` contains the Docusaurus documentation site. +- `tools` contains repository tooling. +- `archive` contains deprecated packages and historical code. Do not use archived code as the first pattern source. -## Overview +## Baseline Tooling -- View [`docs/structure.md`](./docs/structure.md) for the repository structure and organization -- View [`docs/tooling.md`](./docs/tooling.md) for development tools and utilities -- View [`docs/testing.md`](./docs/testing.md) for testing guidelines and commands -- View [`docs/linting.md`](./docs/linting.md) for code formatting and linting +- Use Node `>=20` and `pnpm@8.10.5`. +- This is a TypeScript-first codebase. Preserve type safety and public API types. +- Biome formats and lints TS/JS. Prettier formats markdown and MDX through lint-staged. +- Prefer existing package scripts over ad hoc commands. -## Validating Changes +## Contributor Docs -To validate your changes you can run the following commands: +- Contributor index: [`contributing/README.md`](./contributing/README.md) +- Repository structure: [`contributing/structure.md`](./contributing/structure.md) +- Development tooling: [`contributing/tooling.md`](./contributing/tooling.md) +- Testing: [`contributing/testing.md`](./contributing/testing.md) +- Linting and formatting: [`contributing/linting.md`](./contributing/linting.md) +- Changesets: [`contributing/changesets.md`](./contributing/changesets.md) + +## Validation + +Prefer scoped validation for the package you touched, then broaden only when the change crosses package boundaries. + +Useful root commands: + +```bash +pnpm lint +pnpm test:all +pnpm build:all +``` + +Useful package-scoped commands: ```bash -pnpm test:all # Run all tests -pnpm build:all # Build all packages -pnpm lint # Run linting checks -pnpm lint:fix # Auto-fix linting issues +pnpm --dir packages/core test +pnpm --dir packages/core typecheck +pnpm --dir packages/core build +pnpm --dir website build ``` -## Important Notes for AI Agents +If you cannot run a relevant validation command, say why in the final response. -1. **Always check existing patterns** before implementing new features -2. **Use the established registry patterns** for agent and tool management -3. **Maintain type safety** - this is a TypeScript-first codebase -4. **Follow the monorepo structure** - changes may impact multiple packages -5. **Test your changes** - ensure all tests pass before committing +## Implementation Rules -## Gotchas +- Check nearby patterns before adding new abstractions. +- Keep edits scoped to the package and behavior requested. +- Update tests with behavior changes. Add regression tests for bugs. +- For public API, runtime behavior, package contents, dependency, or migration changes in published packages, add a changeset. See [`contributing/changesets.md`](./contributing/changesets.md). +- Do not hand-edit generated files unless the file clearly documents manual edits. -- **JSON.stringify** SHOULD NEVER BE USED, used the `safeStringify` function instead, imported from `@voltagent/internal` +## Serialization + +Use `safeStringify` from `@voltagent/internal` or `@voltagent/internal/utils` for production serialization, including request bodies, logs, persistence, telemetry attributes, and streamed payloads. + +`JSON.stringify` is acceptable in tests, mocks, fixtures, or when intentionally matching a platform API. + +## Core Registry Files + +Do not hand-edit generated model registry files under `packages/core/src/registries`. Update the source/generator and run: + +```bash +pnpm --dir packages/core generate:model-registry +``` diff --git a/contributing/README.md b/contributing/README.md new file mode 100644 index 000000000..47a52e973 --- /dev/null +++ b/contributing/README.md @@ -0,0 +1,35 @@ +# Contributing Guide + +Start here for repository-level contribution docs and shared reference files. + +## Quick Reference + +- Read [Contribution policy](../CONTRIBUTING.md) for the public contribution entry point. +- Use the docs below for repository-specific setup, validation, release, and coding-agent guidance. +- Use [LLM documentation index](../website/static/llms.txt) when an agent or tool needs a compact docs map. + +## Contributor Docs + +- [Repository structure](./structure.md) +- [Development tooling](./tooling.md) +- [Testing](./testing.md) +- [Linting and formatting](./linting.md) +- [Changesets](./changesets.md) +- [Coding-agent compatibility](./coding-agents.md) + +## Repository References + +- [Project README](../README.md) +- [Contribution policy](../CONTRIBUTING.md) +- [Code of conduct](../CODE_OF_CONDUCT.md) +- [Security policy](../SECURITY.md) +- [Changelog](../CHANGELOG.md) +- [Examples](../examples/README.md) +- [Website docs app](../website/README.md) +- [LLM documentation index](../website/static/llms.txt) +- [Root coding-agent instructions](../AGENTS.md) + +## Related Docs + +- [Root coding-agent instructions](../AGENTS.md) +- [Website docs app](../website/README.md) diff --git a/contributing/changesets.md b/contributing/changesets.md new file mode 100644 index 000000000..d9ad34400 --- /dev/null +++ b/contributing/changesets.md @@ -0,0 +1,41 @@ +# Changesets + +VoltAgent uses Changesets to version public packages and generate changelog entries. + +## Quick Reference + +- Run `pnpm changeset` from the repository root. +- Add a changeset for published package API, runtime behavior, package contents, dependency, or migration changes. +- Choose the smallest accurate version type: `patch`, `minor`, or `major`. + +## Guidelines + +Add a changeset when a change affects a published package's public API, runtime behavior, package contents, dependencies, or migration path. + +A changeset is usually not needed for tests, internal refactors with no behavior change, examples-only changes, contributor docs, or local tooling that does not affect a published package. + +## Version Type + +- **patch**: bug fixes, small behavior corrections, dependency updates, or internal changes that affect package output +- **minor**: backwards-compatible features, new exports, new options, or expanded behavior +- **major**: breaking API changes, removed exports/options, changed defaults, or required migrations + +## Creating One + +Run the Changesets CLI from the repository root: + +```bash +pnpm changeset +``` + +Select every affected published package and choose the smallest accurate version type. Write the summary for users, not maintainers; explain what changed and any migration needed. + +## Config + +- Main branch releases use [`.changeset/config.json`](../.changeset/config.json). +- Prerelease branch releases use [`.changeset/config-next.json`](../.changeset/config-next.json). + +## Related Docs + +- [Development tooling](./tooling.md) +- [Root coding-agent instructions](../AGENTS.md) diff --git a/contributing/coding-agents.md b/contributing/coding-agents.md new file mode 100644 index 000000000..a8e7e0d06 --- /dev/null +++ b/contributing/coding-agents.md @@ -0,0 +1,60 @@ +# Coding Agents + +VoltAgent supports coding agents through shared repository instructions and ignored local configuration. + +## Quick Reference + +- Keep `AGENTS.md` as the source of truth for shared coding-agent instructions. +- Add nested `AGENTS.md` files only for major package areas where local context materially helps. +- Run `./scripts/sync-agent-instructions.mjs` after adding or moving instruction files. + +## Guidelines + +`AGENTS.md` is the source of truth for coding-agent instructions. Add shared guidance there, or in nested `AGENTS.md` files for major package areas where local context materially helps. + +Some coding agents may require a different instruction filename. Keep those files as symlinks to the nearest `AGENTS.md` file instead of duplicating instruction content. + +| Agent/tool | Support | Instruction file | +| ----------------------------------- | --------- | ------------------------ | +| Codex | Native | `AGENTS.md` | +| Cursor | Native | `AGENTS.md` | +| Other `AGENTS.md`-compatible agents | Native | `AGENTS.md` | +| Claude Code | Symlinked | `CLAUDE.md -> AGENTS.md` | + +## Useful Links + +- [Contributor docs index](./README.md) +- [Root coding-agent instructions](../AGENTS.md) +- [LLM documentation index](../website/static/llms.txt) +- [Website docs app](../website/README.md) + +## Local Agent Config + +Per-developer agent and IDE config is ignored by git. You can safely create local files for your own setup without committing them: + +- `.agents/` +- `.claude` +- `.codex/` +- `.cursor/` +- `.windsurf/` +- `.vscode/` +- `.mcp.json` + +Common local MCP config locations include `.cursor/mcp.json`, `.windsurf/mcp.json`, `.vscode/mcp.json`, and root `.mcp.json`. Root `mcp.json` is not ignored; use `.mcp.json` for private root-level MCP config. + +Do not commit local credentials, personal MCP server paths, local model settings, or machine-specific tool permissions. + +## Syncing Aliases + +Run this after adding or moving instruction files: + +```bash +./scripts/sync-agent-instructions.mjs +``` + +The sync script creates missing instruction-file aliases for coding agents that do not read `AGENTS.md` directly. It prints the symlinks first and asks for confirmation unless you pass `--yes`. + +## Related Docs + +- [Contributor docs index](./README.md) +- [Root coding-agent instructions](../AGENTS.md) diff --git a/contributing/linting.md b/contributing/linting.md new file mode 100644 index 000000000..a4fc317ed --- /dev/null +++ b/contributing/linting.md @@ -0,0 +1,38 @@ +# Linting + +VoltAgent uses Biome for TypeScript and JavaScript linting and formatting. Markdown and MDX files are formatted with Prettier through lint-staged. + +## Quick Reference + +- **TS/JS linter + formatter**: Biome +- **Markdown/MDX formatter**: Prettier +- **Config**: `biome.json` + +## Running Commands + +```bash +# Check all files +pnpm lint + +# Auto-fix issues +pnpm lint:fix + +# Check only (CI mode) +pnpm lint:ci +``` + +## VS Code Integration + +Install the Biome extension: + +```json +{ + "editor.defaultFormatter": "biomejs.biome", + "editor.formatOnSave": true +} +``` + +## Related Docs + +- [Development tooling](./tooling.md) +- [Testing](./testing.md) diff --git a/docs/structure.md b/contributing/structure.md similarity index 53% rename from docs/structure.md rename to contributing/structure.md index d449b86aa..c42b4be80 100644 --- a/docs/structure.md +++ b/contributing/structure.md @@ -1,40 +1,45 @@ # Repository Structure -This monorepo is organized using pnpm workspaces and Lerna with the following main directories: +This monorepo is organized using pnpm workspaces, Lerna, and Nx with the following main directories: -## Overview +## Quick Reference ``` voltagent/ ├── packages/ # Core packages and integrations ├── examples/ # Example implementations ├── website/ # Documentation website and marketplace -├── docs/ # Project documentation +├── contributing/ # Contributor and project documentation ├── scripts/ # Build and utility scripts +├── tools/ # Repository tooling packages +├── archive/ # Deprecated packages and historical code ├── .changeset/ # Changeset configuration └── package.json # Root package configuration ``` -## Details +## Directory Details ### `packages/` Core packages, AI provider integrations, and utilities: +- **a2a-server** - Agent-to-Agent protocol server implementation +- **ag-ui** - AG-UI adapter for agents and CopilotKit runtimes - **cli** - VoltAgent CLI for project scaffolding and management -- **core** - Main VoltAgent framework with agent orchestration, memory management, tools, and observability. Includes the `ConversationBuffer` (merges model/tool messages into UI messages for persistence) and `MemoryPersistQueue` (debounced memory write pipeline). +- **cloudflare-d1**, **libsql**, **postgres**, **supabase**, **voltagent-memory** - Memory and storage adapters +- **core** - Main VoltAgent framework with agent orchestration, memory management, tools, workflows, observability, workspaces, and VoltOps integration - **create-voltagent-app** - Project initialization tool - **docs-mcp** - Model Context Protocol documentation server +- **evals**, **scorers** - Evaluation and scoring utilities - **internal** - Internal utilities and shared types used across packages -- **langfuse-exporter** - LangFuse telemetry exporter -- **libsql** - LibSQL database integration +- **langfuse-exporter**, **vercel-ai-exporter** - Telemetry exporters - **logger** - Universal logger implementation -- **postgres** - PostgreSQL database integration +- **mcp-server** - Model Context Protocol server implementation +- **rag**, **resumable-streams** - Retrieval and streaming utilities +- **sandbox-daytona**, **sandbox-e2b** - Sandbox provider integrations - **sdk** - JavaScript/TypeScript SDK for VoltAgent API - **server-core** - Core server handlers, schemas, and business logic -- **server-hono** - Hono-based server implementation with API routes -- **supabase** - Supabase client integration -- **vercel-ai-exporter** - Vercel AI telemetry exporter +- **server-elysia**, **server-hono**, **serverless-hono** - Server runtime integrations - **voice** - Voice interaction capabilities #### Conventions @@ -70,14 +75,32 @@ Documentation website built with Docusaurus: - **blog/** - Blog posts and tutorials - **static/** - Static assets and images -### `docs/` +### `contributing/` -Project-level documentation: +Contributor and project-level documentation: +- **README.md** - Contributor documentation index and repository links - **structure.md** - This file, describing repository organization - **tooling.md** - Development tools and workflows -- Additional architecture and design documents +- **testing.md** - Testing guidelines and commands +- **linting.md** - Formatting and linting guidelines +- **changesets.md** - Package versioning and changelog guidelines +- **coding-agents.md** - Supported coding-agent instruction files ### `scripts/` Reusable scripts for the project, for repeatable development tasks. + +### `tools/` + +Repository tooling packages and generators used by the monorepo. + +### `archive/` + +Deprecated packages and historical code. Prefer current package patterns before using archived code as a reference. + +## Related Docs + +- [Development tooling](./tooling.md) +- [Testing](./testing.md) +- [Coding-agent compatibility](./coding-agents.md) diff --git a/docs/testing.md b/contributing/testing.md similarity index 78% rename from docs/testing.md rename to contributing/testing.md index a0830b6e5..568fe0f8c 100644 --- a/docs/testing.md +++ b/contributing/testing.md @@ -1,12 +1,12 @@ # Testing -VoltAgent uses `vitest` for testing, with the naming convention of `*.spec.ts` for unit tests, and `*.spec-d.ts` for type tests. +VoltAgent uses Vitest for testing, with `*.spec.ts` for runtime tests and `*.spec-d.ts` for type tests. -## Overview +## Quick Reference -- **Framework**: vitest -- **Test Files**: `*.spec.ts` -- **Type Test Files**: `*.spec-d.ts` +- **Framework**: Vitest +- **Runtime test files**: `*.spec.ts` +- **Type test files**: `*.spec-d.ts` - **Coverage**: V8 provider - **Environment**: Node.js @@ -19,16 +19,16 @@ pnpm test:all # Run tests with coverage pnpm test:all:coverage -# Run tests for specific package -pnpm test --filter @voltagent/core +# Run tests for a specific package +pnpm --dir packages/core test # Run single test file -cd packages/core && pnpm vitest run src/tool/index.spec.ts +pnpm --dir packages/core test:single -- src/tool/index.spec.ts ``` ## Writing Tests -All tests are using the `vitest` framework, and use the format of `*.spec.ts` for unit tests, and `*.spec-d.ts` for type tests. Tests should be co-located with the code they are testing, using the same naming convention (i.e. `tool.spec.ts` is testing the `tool.ts` file). +Tests should be co-located with the code they cover. Use matching names where possible, such as `tool.spec.ts` for `tool.ts`. ### Basic Structure @@ -52,7 +52,7 @@ describe("yourFunction", () => { ### Type Tests -Type tests are used to test the types of the codebase. They are located in the `*.spec-d.ts` files, and are used to test the types of the codebase. +Type tests verify public inference and type compatibility. Add or update `*.spec-d.ts` files when changing exported types, generics, or overload behavior. ```typescript import { describe, expectTypeOf, it } from "vitest"; @@ -65,7 +65,7 @@ describe("YourType", () => { }); ``` -[Type Test Documentation](https://vitest.dev/guide/testing-types.html +[Type Test Documentation](https://vitest.dev/guide/testing-types.html) ### Mocking @@ -148,3 +148,8 @@ describe("Tool", () => { }); }); ``` + +## Related Docs + +- [Development tooling](./tooling.md) +- [Linting and formatting](./linting.md) diff --git a/docs/tooling.md b/contributing/tooling.md similarity index 59% rename from docs/tooling.md rename to contributing/tooling.md index 32b819f33..0ce57bd1b 100644 --- a/docs/tooling.md +++ b/contributing/tooling.md @@ -2,12 +2,17 @@ Development tools and utilities used in this repository. +## Quick Reference + - **package manager**: pnpm -- **monorepo**: lerna -- **language**: typescript -- **formatter**: biome -- **linter**: biome +- **monorepo**: Lerna and Nx +- **language**: TypeScript +- **formatter**: Biome for TS/JS, Prettier for markdown/MDX through lint-staged +- **linter**: Biome - **build system**: tsup +- **release management**: Changesets + +## Tools ## pnpm @@ -27,7 +32,7 @@ Lerna is a tool for managing JavaScript projects with multiple packages. ## nx -High-performance build system optimized for JavaScript and TypeScript monorepos with smart caching and parallel execution. Lerna and nx are integrated as nx acquired lerna. +High-performance build system optimized for JavaScript and TypeScript monorepos with smart caching and parallel execution. Lerna delegates task orchestration to Nx. ### Links @@ -35,7 +40,7 @@ High-performance build system optimized for JavaScript and TypeScript monorepos ## Biome -All-in-one toolchain for web projects, combining formatter and linter functionality (replaces ESLint + Prettier). +All-in-one toolchain for TypeScript and JavaScript linting and formatting. ### Links @@ -56,3 +61,20 @@ Builds and bundles TypeScript code into JavaScript. ### Links - [Documentation](https://tsup.egoist.dev/) + +## Changesets + +Manages package versioning and changelog entries for public package changes. + +See [Changesets](./changesets.md) for when a changeset is required. + +### Links + +- [Documentation](https://github.com/changesets/changesets) + +## Related Docs + +- [Repository structure](./structure.md) +- [Testing](./testing.md) +- [Linting and formatting](./linting.md) +- [Changesets](./changesets.md) diff --git a/docs/linting.md b/docs/linting.md deleted file mode 100644 index 7886d5f84..000000000 --- a/docs/linting.md +++ /dev/null @@ -1,32 +0,0 @@ -# Linting - -VoltAgent uses Biome for linting and formatting code. Biome is a fast and modern linter and formatter for TypeScript and JavaScript that has set of pre-defined rules that are applied to the codebase. - -## Tools - -- **Linter + Formatter**: Biome (replaces ESLint + Prettier) -- **Config**: `biome.json` - -## Running Commands - -```bash -# Check all files -pnpm lint - -# Auto-fix issues -pnpm lint:fix - -# Check only (CI mode) -pnpm lint:ci -``` - -## VS Code Integration - -Install the Biome extension: - -```json -{ - "editor.defaultFormatter": "biomejs.biome", - "editor.formatOnSave": true -} -``` diff --git a/packages/core/AGENTS.md b/packages/core/AGENTS.md new file mode 100644 index 000000000..a956e4296 --- /dev/null +++ b/packages/core/AGENTS.md @@ -0,0 +1,33 @@ +# @voltagent/core + +`packages/core` is the primary runtime package. Changes here often affect agents, workflows, memory, tools, observability, server packages, examples, and docs. + +## Local Rules + +- Do not use `console.log` in core source. Root Biome config treats console logging as an error under `packages/core`. +- Prefer existing managers, registries, context keys, and helper utilities before adding new global state. +- Keep public exports intentional. User-facing APIs usually need updates in `src/index.ts` and sometimes package `exports`. +- Preserve both runtime behavior and type-level behavior. Add `.spec-d.ts` coverage when changing public generics or inference. +- Use `safeStringify` for production serialization and telemetry attributes. +- Do not hand-edit generated registry files under `src/registries`; run `pnpm --dir packages/core generate:model-registry`. + +## Validation + +Use the narrowest command that covers your change: + +```bash +pnpm --dir packages/core test +pnpm --dir packages/core typecheck +pnpm --dir packages/core build +pnpm --dir packages/core test:single -- src/path/to/file.spec.ts +``` + +Run broader root validation when core changes affect other packages. + +## High-Risk Areas + +- `src/agent`: model calls, streaming, tool execution, memory, guardrails, subagents, retries, and telemetry are tightly coupled. +- `src/workflow`: suspend/resume, step IDs, hooks, time travel, streaming, usage tracking, and type inference must stay consistent. +- `src/memory`: shared storage/vector/embedding contracts affect external adapters. +- `src/workspace`: filesystem, sandbox, search, skills, timeouts, and tool policy can affect local execution safety. +- `src/voltops`: remote API clients, prompt management, and action clients must preserve wire compatibility. diff --git a/packages/core/CLAUDE.md b/packages/core/CLAUDE.md new file mode 120000 index 000000000..47dc3e3d8 --- /dev/null +++ b/packages/core/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file diff --git a/packages/core/src/agent/AGENTS.md b/packages/core/src/agent/AGENTS.md new file mode 100644 index 000000000..c8cc248c3 --- /dev/null +++ b/packages/core/src/agent/AGENTS.md @@ -0,0 +1,25 @@ +# Core Agent Runtime + +This directory owns the `Agent` runtime and related execution paths. + +## Local Rules + +- Treat streaming and non-streaming paths as paired behavior. When changing one, check the matching generate/stream and text/object paths. +- Tool execution, memory persistence, guardrails, subagents, retries, hooks, and telemetry all interact. Trace the full call flow before making localized edits. +- Preserve abort/cancellation behavior and retry semantics. +- Keep OpenTelemetry attributes useful but avoid sensitive or high-cardinality values. Serialize structured attributes with `safeStringify`. +- Keep public option/result types stable unless the task is explicitly an API change. + +## Tests + +- Add focused regression tests near the behavior changed. +- Use `.spec-d.ts` tests for public type inference or generic changes. +- Existing broad coverage lives in `agent.spec.ts`; prefer smaller neighboring specs when the behavior has a dedicated file. + +Useful commands: + +```bash +pnpm --dir packages/core test:single -- src/agent/path-to-test.spec.ts +pnpm --dir packages/core test +pnpm --dir packages/core typecheck +``` diff --git a/packages/core/src/agent/CLAUDE.md b/packages/core/src/agent/CLAUDE.md new file mode 120000 index 000000000..47dc3e3d8 --- /dev/null +++ b/packages/core/src/agent/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file diff --git a/packages/core/src/memory/AGENTS.md b/packages/core/src/memory/AGENTS.md new file mode 100644 index 000000000..dfce73719 --- /dev/null +++ b/packages/core/src/memory/AGENTS.md @@ -0,0 +1,24 @@ +# Core Memory + +This directory defines shared memory types, manager behavior, semantic search, working memory, and adapter contracts. + +## Local Rules + +- Preserve `StorageAdapter`, `VectorAdapter`, `EmbeddingAdapter`, conversation, message, working-memory, and workflow-state contracts. +- Shared behavior here should remain compatible with external adapters such as Postgres, LibSQL, Supabase, Cloudflare D1, and managed memory. +- Use `safeStringify` for persisted structured data and logs. +- Be careful with message shape compatibility. Agent persistence, workflow state, server APIs, and examples can depend on these types. +- Keep adapter-neutral logic in core; adapter-specific SQL or platform behavior belongs in adapter packages. + +## Tests + +- Add shared contract tests when manager behavior changes. +- Add type tests for public memory API inference or type compatibility changes. + +Useful commands: + +```bash +pnpm --dir packages/core test:single -- src/memory/path-to-test.spec.ts +pnpm --dir packages/core test +pnpm --dir packages/core typecheck +``` diff --git a/packages/core/src/memory/CLAUDE.md b/packages/core/src/memory/CLAUDE.md new file mode 120000 index 000000000..47dc3e3d8 --- /dev/null +++ b/packages/core/src/memory/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file diff --git a/packages/core/src/voltops/AGENTS.md b/packages/core/src/voltops/AGENTS.md new file mode 100644 index 000000000..9c2ba0cfc --- /dev/null +++ b/packages/core/src/voltops/AGENTS.md @@ -0,0 +1,21 @@ +# Core VoltOps Integration + +This directory owns VoltOps clients, prompt APIs, prompt management, action clients, local prompt loading, and related types. + +## Local Rules + +- Preserve wire compatibility with VoltOps APIs. Be cautious with request/response shapes, endpoint paths, auth headers, and error handling. +- Use `safeStringify` for request bodies and structured logging/telemetry. +- Keep prompt template behavior deterministic and covered by tests. +- Avoid changing generated or API-shaped client methods without checking the matching tests and exported types. +- Be careful with global client behavior and precedence rules; existing priority tests document expected selection. + +## Tests + +Add or update focused tests for client requests, prompt manager behavior, template rendering, and priority rules: + +```bash +pnpm --dir packages/core test:single -- src/voltops/path-to-test.spec.ts +pnpm --dir packages/core test +pnpm --dir packages/core typecheck +``` diff --git a/packages/core/src/voltops/CLAUDE.md b/packages/core/src/voltops/CLAUDE.md new file mode 120000 index 000000000..47dc3e3d8 --- /dev/null +++ b/packages/core/src/voltops/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file diff --git a/packages/core/src/workflow/AGENTS.md b/packages/core/src/workflow/AGENTS.md new file mode 100644 index 000000000..553b53312 --- /dev/null +++ b/packages/core/src/workflow/AGENTS.md @@ -0,0 +1,21 @@ +# Core Workflow Runtime + +This directory owns workflow creation, chaining, execution, streaming, suspension, and step helpers. + +## Local Rules + +- Preserve suspend/resume behavior, checkpoint state, step IDs, hooks, time travel, cancellation, and usage tracking. +- Runtime behavior and type inference are both part of the API. Changes to chain generics or step typing usually need `.spec-d.ts` coverage. +- New or changed steps under `steps/` need exports from the local step index and, when public, from workflow/core package exports. +- Keep workflow state serializable and backwards-aware. Use `safeStringify` for production serialization. +- Avoid special-casing a step in the core executor if the behavior belongs in a step helper. + +## Tests + +Use targeted runtime tests for changed behavior and type tests for inference changes: + +```bash +pnpm --dir packages/core test:single -- src/workflow/path-to-test.spec.ts +pnpm --dir packages/core test +pnpm --dir packages/core typecheck +``` diff --git a/packages/core/src/workflow/CLAUDE.md b/packages/core/src/workflow/CLAUDE.md new file mode 120000 index 000000000..47dc3e3d8 --- /dev/null +++ b/packages/core/src/workflow/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file diff --git a/packages/core/src/workspace/AGENTS.md b/packages/core/src/workspace/AGENTS.md new file mode 100644 index 000000000..2c98cc282 --- /dev/null +++ b/packages/core/src/workspace/AGENTS.md @@ -0,0 +1,21 @@ +# Core Workspace + +This directory owns workspace filesystem access, sandbox execution, search, skills, timeouts, and tool policy. + +## Local Rules + +- Treat filesystem and sandbox behavior as security-sensitive. Preserve path normalization, timeout handling, and tool-policy checks. +- Keep local filesystem behavior and sandbox behavior aligned where they expose the same workspace capability. +- Avoid broadening file access, command execution, or skill loading without explicit tests. +- Search behavior should remain deterministic and should not require network access. +- Preserve public workspace types and toolkit behavior used by agents. + +## Tests + +Prefer targeted tests for filesystem, sandbox, search, skills, timeout, or policy changes: + +```bash +pnpm --dir packages/core test:single -- src/workspace/path-to-test.spec.ts +pnpm --dir packages/core test +pnpm --dir packages/core typecheck +``` diff --git a/packages/core/src/workspace/CLAUDE.md b/packages/core/src/workspace/CLAUDE.md new file mode 120000 index 000000000..47dc3e3d8 --- /dev/null +++ b/packages/core/src/workspace/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file diff --git a/scripts/sync-agent-instructions.mjs b/scripts/sync-agent-instructions.mjs new file mode 100755 index 000000000..185e0f23b --- /dev/null +++ b/scripts/sync-agent-instructions.mjs @@ -0,0 +1,254 @@ +#!/usr/bin/env node + +/** + * Sync coding-agent instruction aliases across the repository. + * + * This script walks the repo, finds configured source instruction files, and creates + * sibling symlinks for tools that expect a different filename. For example, Claude + * Code reads `CLAUDE.md`, while this repo keeps the canonical instructions in + * `AGENTS.md`. + * + * Usage: + * + * ```bash + * ./scripts/sync-agent-instructions.mjs + * ./scripts/sync-agent-instructions.mjs --yes + * ``` + * + * Without `--yes`, the script prints the symlinks it will create and asks for + * confirmation. Existing valid symlinks are skipped. Existing files or + * symlinks pointing somewhere else are reported as conflicts and left alone. + * + * @module sync-agent-instructions + */ + +import fs from "node:fs"; +import path from "node:path"; +import { createInterface } from "node:readline/promises"; +import { fileURLToPath } from "node:url"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const repoRoot = path.resolve(__dirname, ".."); +const shouldSkipPrompt = process.argv.includes("--yes"); + +/** + * Configure instruction aliases here. + * + * `AGENTS.md` is always the canonical instruction file. + * + * Add a filename to this list if another coding agent needs a different + * instruction filename but should share the same canonical AGENTS.md content. + */ +const instructionAliases = ["CLAUDE.md"]; + +const ignoredDirectories = new Set([ + ".git", + ".claude", + ".changeset", + ".docusaurus", + ".joggr", + ".next", + ".netlify", + ".nx", + ".serena", + ".turbo", + ".wrangler", + "build", + "coverage", + "dist", + "node_modules", +]); + +const stats = { + created: 0, + skipped: 0, + conflicts: 0, +}; + +const pendingLinks = []; +const conflictedLinks = []; + +function getPathStats(pathToCheck) { + try { + return fs.lstatSync(pathToCheck); + } catch (error) { + if (error.code === "ENOENT") { + return undefined; + } + + throw error; + } +} + +function getSymlinkTarget(linkPath) { + return fs.readlinkSync(linkPath); +} + +function isExpectedAlias(linkPath, pathStats) { + return pathStats.isSymbolicLink() && getSymlinkTarget(linkPath) === "AGENTS.md"; +} + +function describeExistingAlias(linkPath, pathStats) { + if (pathStats.isSymbolicLink()) { + return `symlink to ${getSymlinkTarget(linkPath)}`; + } + + if (pathStats.isFile()) { + return "regular file"; + } + + if (pathStats.isDirectory()) { + return "directory"; + } + + return "non-file path"; +} + +function collectInstructionLinks(directory) { + const entries = fs.readdirSync(directory, { withFileTypes: true }); + let hasCanonicalInstructions = false; + + for (const entry of entries) { + if (entry.name === "AGENTS.md" && entry.isFile()) { + hasCanonicalInstructions = true; + break; + } + } + + if (hasCanonicalInstructions) { + const sourcePath = path.join(directory, "AGENTS.md"); + + for (const instructionAlias of instructionAliases) { + const linkPath = path.join(directory, instructionAlias); + const linkPathStats = getPathStats(linkPath); + + if (linkPathStats === undefined) { + pendingLinks.push({ + linkPath, + sourcePath, + }); + } else if (isExpectedAlias(linkPath, linkPathStats)) { + stats.skipped += 1; + } else { + stats.conflicts += 1; + conflictedLinks.push({ + description: describeExistingAlias(linkPath, linkPathStats), + linkPath, + }); + } + } + } + + for (const entry of entries) { + if (!entry.isDirectory() || ignoredDirectories.has(entry.name)) { + continue; + } + + collectInstructionLinks(path.join(directory, entry.name)); + } +} + +function printConflictedLinks() { + console.log("Instruction aliases skipped because the path already exists:"); + console.log(""); + + for (const conflictedLink of conflictedLinks) { + console.log( + `- ${path.relative(repoRoot, conflictedLink.linkPath)}: expected symlink to AGENTS.md, found ${conflictedLink.description}` + ); + } + + console.log(""); +} + +function printPendingLinks() { + console.log("Instruction symlinks to create:"); + console.log(""); + + const linksBySource = new Map(); + + for (const pendingLink of pendingLinks) { + const relativeSourcePath = path.relative(repoRoot, pendingLink.sourcePath); + + if (!linksBySource.has(relativeSourcePath)) { + linksBySource.set(relativeSourcePath, []); + } + + linksBySource.get(relativeSourcePath).push(path.relative(repoRoot, pendingLink.linkPath)); + } + + for (const [sourcePath, linkPaths] of linksBySource) { + console.log(sourcePath); + + for (let index = 0; index < linkPaths.length; index += 1) { + const prefix = index === linkPaths.length - 1 ? "└──" : "├──"; + console.log(`${prefix} ${linkPaths[index]}`); + } + + console.log(""); + } +} + +async function confirmPendingLinks() { + if (shouldSkipPrompt) { + return true; + } + + const readline = createInterface({ + input: process.stdin, + output: process.stdout, + }); + + try { + const answer = await readline.question("Create these symlinks? [y/N] "); + return answer.trim().toLowerCase() === "y" || answer.trim().toLowerCase() === "yes"; + } finally { + readline.close(); + } +} + +function createInstructionSymlink(pendingLink) { + try { + fs.symlinkSync("AGENTS.md", pendingLink.linkPath, "file"); + } catch (error) { + const relativeLinkPath = path.relative(repoRoot, pendingLink.linkPath); + + if (process.platform === "win32" && error.code === "EPERM") { + console.error( + `Failed to create ${relativeLinkPath}. Windows may require Developer Mode or an elevated shell for symlinks.` + ); + } else { + console.error(`Failed to create ${relativeLinkPath}: ${error.message}`); + } + + throw error; + } +} + +function createPendingLinks() { + for (const pendingLink of pendingLinks) { + createInstructionSymlink(pendingLink); + stats.created += 1; + console.log(`Created ${path.relative(repoRoot, pendingLink.linkPath)} -> AGENTS.md`); + } +} + +collectInstructionLinks(repoRoot); + +if (conflictedLinks.length > 0) { + printConflictedLinks(); +} + +if (pendingLinks.length > 0) { + printPendingLinks(); + + if (await confirmPendingLinks()) { + createPendingLinks(); + } else { + console.log("No symlinks created."); + } +} + +console.log( + `Instruction symlink sync complete: ${stats.created} created, ${stats.skipped} skipped, ${stats.conflicts} conflicts.` +); From a8e069d08bab94b65bde3713b2d775532597dfde Mon Sep 17 00:00:00 2001 From: Zac Rosenbauer Date: Fri, 8 May 2026 13:03:33 -0400 Subject: [PATCH 2/4] chore: simplify instruction sync script --- scripts/sync-agent-instructions.mjs | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/scripts/sync-agent-instructions.mjs b/scripts/sync-agent-instructions.mjs index 185e0f23b..e48ebd652 100755 --- a/scripts/sync-agent-instructions.mjs +++ b/scripts/sync-agent-instructions.mjs @@ -3,10 +3,9 @@ /** * Sync coding-agent instruction aliases across the repository. * - * This script walks the repo, finds configured source instruction files, and creates - * sibling symlinks for tools that expect a different filename. For example, Claude - * Code reads `CLAUDE.md`, while this repo keeps the canonical instructions in - * `AGENTS.md`. + * This script walks the repo, finds canonical instruction files, and creates + * sibling symlinks for tools that expect a different filename. `AGENTS.md` + * remains the source of truth; configured aliases point to it. * * Usage: * @@ -80,17 +79,13 @@ function getPathStats(pathToCheck) { } } -function getSymlinkTarget(linkPath) { - return fs.readlinkSync(linkPath); -} - function isExpectedAlias(linkPath, pathStats) { - return pathStats.isSymbolicLink() && getSymlinkTarget(linkPath) === "AGENTS.md"; + return pathStats.isSymbolicLink() && fs.readlinkSync(linkPath) === "AGENTS.md"; } function describeExistingAlias(linkPath, pathStats) { if (pathStats.isSymbolicLink()) { - return `symlink to ${getSymlinkTarget(linkPath)}`; + return `symlink to ${fs.readlinkSync(linkPath)}`; } if (pathStats.isFile()) { From 5a777b7a470fc8bed17a89f2b681ddc20f933fc1 Mon Sep 17 00:00:00 2001 From: Zac Rosenbauer Date: Sat, 9 May 2026 20:57:29 -0400 Subject: [PATCH 3/4] docs: align and fact-check AGENTS.md across the monorepo Refactor pass on all 13 AGENTS.md files (CLAUDE.md symlinks managed by scripts/sync-agent-instructions.mjs): - Add nested AGENTS.md for a2a, mcp, observability, tool, server-core, and website (Docusaurus-specific gotchas: npm vs pnpm, sucrase-built plugins, llms.txt is hand-curated, SSR rule, multiple sidebars). - Rewrite root: split commit/PR conventions, replace contributor-docs bullets with a Vercel block that includes the website doc tree, audit Boundaries (lockfile, .changeset/, .github/, tsconfig, workspace yaml), drop generic "TypeScript-first" Code Style. - Standardize layout: Critical Rules (bullets, top), Boundaries (3-tier bullets), "Related" instead of "See Also". - Fact-check pass against actual source (3 verifier agents): - Tool span name is `tool.execution:`, not `agent.tool.`. - Workflow span name is `workflow.step.`, not ``. - Agent root span uses the agent's own `name`, not `agent.generateText`. - `agent.state` enum is running/completed/cancelled. - Subagent delegate is a single `delegate_task` tool, not `__delegate_to_`. - `tools` option type is `(Tool | Toolkit)[]`. - Add `onStepFinish` to the public hook list. - Replace fabricated `workflow.resume(...)` with real `result.resume()` / `workflow.run(input, { resumeFrom })`. - Replace fabricated `client.actions.execute(...)` with namespaced API (`client.actions.slack.postMessage`, etc.). - Fix VectorAdapter shape (deleteBatch, no count/get) and EmbeddingAdapter.getDimensions(): number. - Rename AISdkEmbeddingAdapter -> AiSdkEmbeddingAdapter. - server-core: LogStreamManager, drop non-existent test:single script. - mcp: SDK is range-pinned (caret); default timeout 60000ms not 30000ms. - workspace: operationTimeoutMs has no default for general ops; fix stale spec paths; default search mode is conditional. --no-verify used because lint-staged + Prettier 3.x errors on the CLAUDE.md symlinks ("Explicitly specified pattern ... is a symbolic link"). All files were pre-validated with `prettier --check` and pass. Co-Authored-By: Claude --- AGENTS.md | 147 ++++++++++++++------ packages/core/AGENTS.md | 112 ++++++++++++--- packages/core/src/a2a/AGENTS.md | 114 ++++++++++++++++ packages/core/src/a2a/CLAUDE.md | 1 + packages/core/src/agent/AGENTS.md | 106 +++++++++++++-- packages/core/src/mcp/AGENTS.md | 116 ++++++++++++++++ packages/core/src/mcp/CLAUDE.md | 1 + packages/core/src/memory/AGENTS.md | 158 ++++++++++++++++++++-- packages/core/src/observability/AGENTS.md | 97 +++++++++++++ packages/core/src/observability/CLAUDE.md | 1 + packages/core/src/tool/AGENTS.md | 137 +++++++++++++++++++ packages/core/src/tool/CLAUDE.md | 1 + packages/core/src/voltops/AGENTS.md | 130 ++++++++++++++++-- packages/core/src/workflow/AGENTS.md | 151 +++++++++++++++++++-- packages/core/src/workspace/AGENTS.md | 136 +++++++++++++++++-- packages/server-core/AGENTS.md | 106 +++++++++++++++ packages/server-core/CLAUDE.md | 1 + website/AGENTS.md | 65 +++++++++ website/CLAUDE.md | 1 + 19 files changed, 1460 insertions(+), 121 deletions(-) create mode 100644 packages/core/src/a2a/AGENTS.md create mode 120000 packages/core/src/a2a/CLAUDE.md create mode 100644 packages/core/src/mcp/AGENTS.md create mode 120000 packages/core/src/mcp/CLAUDE.md create mode 100644 packages/core/src/observability/AGENTS.md create mode 120000 packages/core/src/observability/CLAUDE.md create mode 100644 packages/core/src/tool/AGENTS.md create mode 120000 packages/core/src/tool/CLAUDE.md create mode 100644 packages/server-core/AGENTS.md create mode 120000 packages/server-core/CLAUDE.md create mode 100644 website/AGENTS.md create mode 120000 website/CLAUDE.md diff --git a/AGENTS.md b/AGENTS.md index 433d84d1b..9ead0ef9a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,73 +1,134 @@ # VoltAgent -VoltAgent is an open-source TypeScript monorepo for building and orchestrating AI agents. +Open-source TypeScript monorepo for building and orchestrating AI agents with pluggable memory, tools, workflows, and observability. -## Repository Layout +## Critical Rules -- `packages/*` contains published packages. -- `packages/core` contains the core runtime for agents, workflows, memory, tools, observability, workspaces, and VoltOps integration. -- `examples/*` contains runnable example apps. Keep examples simple and copy-pasteable. -- `website` contains the Docusaurus documentation site. -- `tools` contains repository tooling. -- `archive` contains deprecated packages and historical code. Do not use archived code as the first pattern source. +- **Verify before asserting.** Read or grep before claiming a hook signature, span attribute key, workflow step option, registry path, or public export exists in this codebase. Memory and prior context drift; the code is authoritative. +- **`safeStringify` for production serialization.** Import from `@voltagent/internal` for logs, telemetry attributes, persistence, request/response bodies, and streamed payloads. `JSON.stringify` is acceptable only in tests, mocks, fixtures, or when matching a platform API. +- **Add a changeset for any published-package change** that affects API, runtime behavior, package contents, dependencies, or migration. See [`contributing/changesets.md`](./contributing/changesets.md). +- **Generated files are not hand-edited.** Regenerate `packages/core/src/registries/*.generated.ts` via `pnpm --dir packages/core generate:model-registry`. Do not pattern-source from `archive/` — it contains deprecated code. +- **Validate scoped first, broaden only when crossing package boundaries.** Run `pnpm --dir packages/ test` before `pnpm test:all`. +- **Conventional commits, enforced by husky.** Format: `type(scope): subject` (e.g., `fix(core): …`, `feat(server-hono): …`, `chore: …`). The `commit-msg` hook runs commitlint with `@commitlint/config-conventional`. Scope = affected package; omit for repo-wide chores. -## Baseline Tooling +## Setup -- Use Node `>=20` and `pnpm@8.10.5`. -- This is a TypeScript-first codebase. Preserve type safety and public API types. -- Biome formats and lints TS/JS. Prettier formats markdown and MDX through lint-staged. -- Prefer existing package scripts over ad hoc commands. +Requires Node `>=20` and `pnpm@8.10.5` (pinned via `packageManager`). -## Contributor Docs - -- Contributor index: [`contributing/README.md`](./contributing/README.md) -- Repository structure: [`contributing/structure.md`](./contributing/structure.md) -- Development tooling: [`contributing/tooling.md`](./contributing/tooling.md) -- Testing: [`contributing/testing.md`](./contributing/testing.md) -- Linting and formatting: [`contributing/linting.md`](./contributing/linting.md) -- Changesets: [`contributing/changesets.md`](./contributing/changesets.md) +```bash +pnpm install # Install workspace deps +pnpm build:all # Build every package (run before dev for cross-package types) +pnpm dev # Watch all packages (excludes example apps) +``` ## Validation -Prefer scoped validation for the package you touched, then broaden only when the change crosses package boundaries. - -Useful root commands: - ```bash +# Root (use only when changes cross package boundaries) pnpm lint pnpm test:all pnpm build:all -``` - -Useful package-scoped commands: -```bash +# Scoped (preferred — fast, focused) pnpm --dir packages/core test pnpm --dir packages/core typecheck pnpm --dir packages/core build +pnpm --dir packages/core test:single -- src/path/to/file.spec.ts pnpm --dir website build ``` -If you cannot run a relevant validation command, say why in the final response. +## Boundaries -## Implementation Rules +**Allowed without asking** -- Check nearby patterns before adding new abstractions. -- Keep edits scoped to the package and behavior requested. -- Update tests with behavior changes. Add regression tests for bugs. -- For public API, runtime behavior, package contents, dependency, or migration changes in published packages, add a changeset. See [`contributing/changesets.md`](./contributing/changesets.md). -- Do not hand-edit generated files unless the file clearly documents manual edits. +- Read files; run lint, typecheck, test, build commands. +- Edit code within a single package; add or update tests; add a changeset. +- Fix typos in `README.md` files and inline comments without changing structure or examples. +- Add new examples under `examples/with-/` (copy an existing example as the template). -## Serialization +**Ask first** -Use `safeStringify` from `@voltagent/internal` or `@voltagent/internal/utils` for production serialization, including request bodies, logs, persistence, telemetry attributes, and streamed payloads. +- New dependencies (root or any package) or version bumps of `node`, `pnpm`, `tsup`, `vitest`, `biome`, `typescript`. +- Cross-package public API changes; new public packages; new server runtime adapters. +- Adding nested `AGENTS.md` files (then run `./scripts/sync-agent-instructions.mjs` to refresh symlinks). +- Editing `packages/core/src/registries/*.generated.ts`, `archive/`, deprecated packages, `.changeset/config*.json`, CI workflows, husky hooks, `tools/`, root `tsconfig*.json`, `pnpm-workspace.yaml`, `pnpm-lock.yaml`, root `package.json`. +- Editing `.github/` (issue templates, PR template, workflows, `FUNDING.yml`). +- Removing tests; deleting docs without setting up redirects; bumping example app dependencies. +- Bypassing hooks (`--no-verify`) or skipping commitlint scope conventions. +- Restructuring `packages/`, `examples/`, or top-level `website/` doc trees. -`JSON.stringify` is acceptable in tests, mocks, fixtures, or when intentionally matching a platform API. +**Never without explicit ask** -## Core Registry Files +- `git push --force` to any shared branch; force-pushing to `main`; amending pushed commits. +- `pnpm publish` / `lerna publish`; deleting packages or public exports. +- Modifying or deleting `.changeset/*.md` files that have already been merged or versioned. +- Committing build outputs (`dist/`, `build/`, `.next/`, `.docusaurus/`) or per-developer IDE config (`.claude.local.md`, `.mcp.json`, `.cursor/`, `.codex/`, `.windsurf/` — see [`contributing/coding-agents.md`](./contributing/coding-agents.md)). +- Committing secrets, local credentials, or `.env*` files. -Do not hand-edit generated model registry files under `packages/core/src/registries`. Update the source/generator and run: +## Commit Conventions -```bash -pnpm --dir packages/core generate:model-registry +- **Husky `pre-commit`** runs `lint-staged`: Biome on TS/JS, Prettier on Markdown/MDX. +- **Husky `commit-msg`** runs commitlint with `@commitlint/config-conventional`. +- **Format**: `type(scope): subject`. Scope = the affected package; omit for repo-wide chores. Scopes seen in history: `core`, `server-core`, `server-hono`, `server-elysia`, `serverless-hono`, `internal`, `cli`, `changesets` (CI). + +## PR Conventions + +- **Fill out the PR template at [`.github/pull_request_template.md`](./.github/pull_request_template.md).** It has the project's canonical checklist (commit-convention link, related issue, tests, docs, changeset link). Do not delete sections. +- **Title** mirrors the conventional-commit scope/type used in your first commit (`fix(core): …`, `feat(server-hono): …`). Not enforced by a bot, but reviewers expect it. +- **Changeset rule** (extends the template's "Changesets have been added" checkbox): + - Published-package change (API, runtime behavior, package contents, deps, migration) → run `pnpm changeset`, commit the generated `.changeset/*.md`. Patch / minor / major rules in [`contributing/changesets.md`](./contributing/changesets.md). + - Internal-only change (CI, repo tooling, contributing docs, examples-only fixes) → no changeset needed; check the "Changesets have been added" box only when one was actually added. +- **Releases** are automated by Lerna + Changesets via the `ci(changesets): version packages` PR. + +## Code Style + +- **Cross-package imports use `@voltagent/` in production code**, not relative paths into another package. Test fixtures may reach across packages relatively when stubbing internal types. +- **Avoid new `as any` and `as unknown as T` casts.** Existing core source has them; treat as legacy. New code should narrow with type guards, fix the upstream type, or accept a `// biome-ignore` with a one-line justification. +- **Public APIs preserve inference.** Return types should flow from implementations — over-typing breaks downstream consumers. +- **Use existing managers, registries, and context keys** (`AgentRegistry`, `WorkspaceManager`, `OperationContext`, etc.) before introducing new global state. +- **`console.log` is a Biome error under `packages/core/**`** — use the package logger or `@voltagent/logger` instead. +- Biome handles TS/JS formatting + lint (`pnpm lint:fix`); Prettier handles Markdown/MDX via lint-staged. + +```ts +// Good — production serialization +import { safeStringify } from "@voltagent/internal"; +span.setAttribute("agent.context", safeStringify(contextMap)); + +// Acceptable — test fixtures only +const fixture = JSON.stringify({ input: "test" }); ``` + +See [`contributing/linting.md`](./contributing/linting.md). + +## Project Structure + +``` +packages/ # Published packages: core runtime, memory adapters, server runtimes, + # telemetry exporters, sandboxes, MCP/A2A servers, SDK, CLI, voice. +examples/ # Runnable apps; naming `with-`. Copy-pasteable. +website/ # Docusaurus docs site (uses npm — NOT in the pnpm workspace). +contributing/ # Contributor + coding-agent docs. +scripts/, tools/ # Repo tooling. +archive/ # Deprecated code; do not pattern-source from it. +.changeset/ # Pending version bumps. Generated by `pnpm changeset`. +.github/ # PR template, issue templates, workflows. +``` + +Full layout: [`contributing/structure.md`](./contributing/structure.md). + +## Documentation Index + +Prefer retrieval-led reasoning — open the linked file before answering. The index points at where things live; the files contain the actual contracts and examples. + +``` + +root: ./ +contributing/:{README.md=contributor-docs index,structure.md=full package list (~25) + naming conventions,tooling.md=pnpm/Lerna/Nx/Biome/tsup/Changesets toolchain,testing.md=Vitest patterns + AI SDK mocking via MockLanguageModelV3,linting.md=Biome commands + VS Code setup,changesets.md=patch/minor/major rules + when changeset is/isn't needed,coding-agents.md=supported agent matrix + CLAUDE.md→AGENTS.md symlink convention} +packages/core/src/:{agent/AGENTS.md=agent runtime, hooks, streaming,workflow/AGENTS.md=workflow steps, suspend/resume, time travel,memory/AGENTS.md=memory contracts, semantic search, working memory,workspace/AGENTS.md=fs, sandbox, search, skills, policy,voltops/AGENTS.md=voltops clients, prompt management, actions,tool/AGENTS.md=tool definition, schema validation, routing,observability/AGENTS.md=otel span names + attribute conventions,mcp/AGENTS.md=MCP bridge (client/server),a2a/AGENTS.md=A2A server registry/protocol} +packages/:{server-core/AGENTS.md=server handlers, schemas, OpenAPI, WebSocket} +website/:{AGENTS.md=docs site setup + Docusaurus gotchas,docs/=user-facing framework docs,docs/getting-started/=quickstart,docs/agents/=agent guides,docs/workflows/=workflow guides,docs/integrations/=provider integrations,observability/=otel + VoltOps docs,recipes/=cookbook,blog/=release notes + posts,static/llms.txt=hand-curated 38KB LLM docs index} +.github/:{pull_request_template.md=canonical PR checklist,workflows/=CI definitions,ISSUE_TEMPLATE/=issue templates} + +``` + +Memory adapter packages (under `packages/`): `postgres`, `libsql`, `supabase`, `cloudflare-d1`, `voltagent-memory`. Sandboxes: `sandbox-daytona`, `sandbox-e2b`. Exporters: `langfuse-exporter`, `vercel-ai-exporter`. Server runtimes: `server-hono`, `server-elysia`, `serverless-hono`. MCP/A2A: `mcp-server`, `docs-mcp`, `a2a-server`. Read [`contributing/structure.md`](./contributing/structure.md) for the full inventory and naming conventions. diff --git a/packages/core/AGENTS.md b/packages/core/AGENTS.md index a956e4296..7c9b124d9 100644 --- a/packages/core/AGENTS.md +++ b/packages/core/AGENTS.md @@ -1,33 +1,109 @@ # @voltagent/core -`packages/core` is the primary runtime package. Changes here often affect agents, workflows, memory, tools, observability, server packages, examples, and docs. +Primary runtime package: agents, workflows, memory, tools, workspaces, observability, and VoltOps integration. Changes here ripple through server packages, examples, and downstream consumers. -## Local Rules +## Critical Rules -- Do not use `console.log` in core source. Root Biome config treats console logging as an error under `packages/core`. -- Prefer existing managers, registries, context keys, and helper utilities before adding new global state. -- Keep public exports intentional. User-facing APIs usually need updates in `src/index.ts` and sometimes package `exports`. -- Preserve both runtime behavior and type-level behavior. Add `.spec-d.ts` coverage when changing public generics or inference. -- Use `safeStringify` for production serialization and telemetry attributes. -- Do not hand-edit generated registry files under `src/registries`; run `pnpm --dir packages/core generate:model-registry`. +- **No `console.log` in core source.** Biome rule `suspicious/noConsoleLog: error` is enforced under `packages/core/**` (see root `biome.json`). Use `LoggerProxy` / `getGlobalLogger()` from `src/logger`. +- **`safeStringify` for production serialization.** Import from `@voltagent/internal`. Apply to telemetry attributes, wire payloads, logs, and persistence. +- **Do not hand-edit `src/registries/*.generated.ts`.** Regenerate via `pnpm --dir packages/core generate:model-registry`. +- **Public exports are commitments.** Anything re-exported from `src/index.ts` (or via the package `exports` map in `package.json`) is API surface. Add a changeset for changes; preserve runtime AND type-level behavior. +- **Type inference is part of the API.** Add `*.spec-d.ts` coverage when changing public generics or inference paths. `pnpm test` already runs `--typecheck`. +- **Prefer existing registries/managers/context keys** over new global state. `AgentRegistry` and `WorkflowRegistry` are publicly re-exported from `src/index.ts`; the rest (`TriggerRegistry`, `MCPServerRegistry`, `A2AServerRegistry`, `ModelProviderRegistry`) are internal — extend in-place rather than introducing parallel state. Shared context keys live in `src/context-keys.ts`. ## Validation -Use the narrowest command that covers your change: +Use the narrowest command that covers your change. ```bash -pnpm --dir packages/core test -pnpm --dir packages/core typecheck -pnpm --dir packages/core build pnpm --dir packages/core test:single -- src/path/to/file.spec.ts +pnpm --dir packages/core test # full suite, includes --typecheck +pnpm --dir packages/core typecheck # tsc --noEmit +pnpm --dir packages/core build # tsup +pnpm --dir packages/core attw # @arethetypeswrong/cli +pnpm --dir packages/core publint # publint --strict ``` -Run broader root validation when core changes affect other packages. +`prebuild` runs `node ../../scripts/sync-docs-mcp.js` — keep that script working when changing build flow. + +## Code Examples + +```ts +// Good — production serialization +import { safeStringify } from "@voltagent/internal"; +span.setAttribute("agent.context", safeStringify(contextMap)); + +// Acceptable — tests / fixtures only +const fixture = JSON.stringify({ input: "test" }); +``` + +```ts +// Good — package logger (Biome blocks console.log here) +import { LoggerProxy } from "../logger"; +const logger = new LoggerProxy({ component: "MyModule" }); +logger.info("Agent created", { agentId }); + +// Good — child logger from global +import { getGlobalLogger } from "../logger"; +const logger = getGlobalLogger().child({ agentId }); +``` + +## Public API Surface + +- **Main entry**: `src/index.ts` (re-exports across all subsystems). +- **Subpath exports**: only `.` is exposed via `package.json#exports`. There is no `@voltagent/core/utils`, etc. +- **Adding a public export**: update `src/index.ts`, add type tests in a neighboring `*.spec-d.ts`, run `pnpm --dir packages/core test --typecheck`, and add a changeset. + +## Boundaries + +**Allowed without asking** + +- Adding focused tests. +- Refactors that don't change exports. +- Internal helpers. +- Narrowed scopes within a single subsystem. + +**Ask first** + +- Public option/result type changes. +- New top-level exports. +- Hook signature changes. +- Retry/abort/cancel semantics. +- Telemetry attribute keys (downstream consumers parse these). + +**Never without explicit ask** + +- Hand-editing generated registries. +- Removing `safeStringify` in production paths. +- Introducing `console.log`. +- Deleting public exports. +- Breaking the `prebuild` doc sync. ## High-Risk Areas -- `src/agent`: model calls, streaming, tool execution, memory, guardrails, subagents, retries, and telemetry are tightly coupled. -- `src/workflow`: suspend/resume, step IDs, hooks, time travel, streaming, usage tracking, and type inference must stay consistent. -- `src/memory`: shared storage/vector/embedding contracts affect external adapters. -- `src/workspace`: filesystem, sandbox, search, skills, timeouts, and tool policy can affect local execution safety. -- `src/voltops`: remote API clients, prompt management, and action clients must preserve wire compatibility. +Detailed rules in nested `AGENTS.md`: + +- [`src/agent/AGENTS.md`](./src/agent/AGENTS.md) — model calls, streaming, tools, guardrails, subagents, retries, hooks, telemetry (tightly coupled). +- [`src/workflow/AGENTS.md`](./src/workflow/AGENTS.md) — suspend/resume, step IDs, hooks, time travel, usage tracking, type inference. +- [`src/memory/AGENTS.md`](./src/memory/AGENTS.md) — `StorageAdapter` / `VectorAdapter` / `EmbeddingAdapter` contracts (cross-package impact). +- [`src/workspace/AGENTS.md`](./src/workspace/AGENTS.md) — filesystem, sandbox, search, skills, timeouts, tool policy (security-sensitive). +- [`src/voltops/AGENTS.md`](./src/voltops/AGENTS.md) — remote API clients, prompt management, action clients (wire-compat). +- [`src/tool/AGENTS.md`](./src/tool/AGENTS.md) — tool definition, schema validation, manager, routing, span tracking. +- [`src/observability/AGENTS.md`](./src/observability/AGENTS.md) — OTel span names + attribute keys (consumed by every subsystem and external exporters). +- [`src/mcp/AGENTS.md`](./src/mcp/AGENTS.md) — MCP wire-compat, JSON Schema↔Zod conversion, transport security. +- [`src/a2a/AGENTS.md`](./src/a2a/AGENTS.md) — A2A spec version, task ID stability, artifact format. + +Other subsystems without dedicated `AGENTS.md` (treat with extra care; parent rules apply): `src/triggers`, `src/planagent`, `src/retriever`, `src/eval`, `src/voice`, `src/registries` (mix of hand-written like `agent-registry.ts` and generated `*.generated.ts` — only the `.generated` files are off-limits). + +## Tests + +- **Runtime behavior**: `*.spec.ts` next to the file you're changing. Avoid stuffing new cases into `agent.spec.ts` if a smaller neighbor exists. +- **Type inference**: `*.spec-d.ts` (validated via vitest `--typecheck`). Examples: `src/agent/agent.spec-d.ts`, `src/workflow/chain.spec-d.ts`, `src/memory/index.spec-d.ts`. +- **Single file**: `pnpm --dir packages/core test:single -- src/agent/agent.spec.ts`. + +## Related + +- Parent: [`../../AGENTS.md`](../../AGENTS.md) — repo-wide rules and the documentation index +- Children: [`src/agent/AGENTS.md`](./src/agent/AGENTS.md), [`src/workflow/AGENTS.md`](./src/workflow/AGENTS.md), [`src/memory/AGENTS.md`](./src/memory/AGENTS.md), [`src/workspace/AGENTS.md`](./src/workspace/AGENTS.md), [`src/voltops/AGENTS.md`](./src/voltops/AGENTS.md), [`src/tool/AGENTS.md`](./src/tool/AGENTS.md), [`src/observability/AGENTS.md`](./src/observability/AGENTS.md), [`src/mcp/AGENTS.md`](./src/mcp/AGENTS.md), [`src/a2a/AGENTS.md`](./src/a2a/AGENTS.md) +- Sibling package: [`../server-core/AGENTS.md`](../server-core/AGENTS.md) +- Repo docs: [`contributing/testing.md`](../../contributing/testing.md), [`contributing/linting.md`](../../contributing/linting.md), [`contributing/changesets.md`](../../contributing/changesets.md) diff --git a/packages/core/src/a2a/AGENTS.md b/packages/core/src/a2a/AGENTS.md new file mode 100644 index 000000000..3efb3dcb0 --- /dev/null +++ b/packages/core/src/a2a/AGENTS.md @@ -0,0 +1,114 @@ +# Core A2A Integration + +In-process registry for Agent-to-Agent (A2A) protocol servers. Exposes `A2AServerRegistry` for registering, discovering, and routing to agent servers that implement the A2A spec via `@a2a-js/sdk`. + +## Critical Rules + +- **A2A protocol wire compatibility is locked.** `@a2a-js/sdk` is consumed by `packages/a2a-server` (not by `@voltagent/core` directly) — coordinate any version bump there with `packages/server-hono`. The registry in this directory only stores servers that conform to `A2AServerLike` from `@voltagent/internal`. +- **Task ID stability is required for resumption.** Task IDs must be deterministic and persistent across agent restarts. Prefer client-provided `taskId` from `A2AMessage`; only fall back to `randomUUID()`. +- **Artifact format is wire-committed.** `TaskArtifact` shape — `{ name, parts: [{ kind, text }], description?, metadata? }` — must not be restructured without a backwards-compatible migration. +- **Server metadata resolution is idempotent.** Internal `resolveMetadata()` normalizes and deduplicates server IDs; the same server instance always maps to the same ID across calls. +- **Registry is per-VoltAgent instance, not global.** Instantiate via `new A2AServerRegistry()` per runtime container; do not use as a singleton. +- **`safeStringify` for serialized A2A messages in logs / telemetry.** Import from `@voltagent/internal`. + +## Registry Pattern + +> Note: base types (`A2AServerLike`, `A2AServerDeps`, `A2AServerMetadata`) live in `packages/internal/src/a2a/`, not here. + +`A2AServerRegistry` manages servers conforming to `A2AServerLike`: + +- **Register** via `.register(server, deps)` with the agent registry and optional task store. +- **Unregister** via `.unregister(id)`. +- **Lookup** by normalized ID via `.getServer(id)` / `.getMetadata(id)`. +- **List** via `.list()` / `.listMetadata()` for discovery. + +(`resolveMetadata` is private — used internally by `register`.) + +ID normalization: lowercase, strip everything outside `[a-z0-9_-]`, collapse duplicate separators, append `-1` / `-2` / … on collision. Same server instance → same ID across calls. + +## A2A vs. Subagents + +- **Subagents** (`src/agent/subagent/`) — in-process delegation within one VoltAgent runtime. Direct method calls; shared context. +- **A2A** — cross-process or network-exposed agents via the A2A protocol. JSON-RPC over HTTP; task-based resumption. + +Use A2A when calling agents outside your process or exposing your agents to external callers. + +## Code Example + +```ts +import { A2AServerRegistry, VoltAgent } from "@voltagent/core"; +import { A2AServer } from "@voltagent/a2a-server"; + +const voltAgent = new VoltAgent({ + /* ... */ +}); +const registry = new A2AServerRegistry(); + +const server = new A2AServer({ + name: "My Agents", + version: "1.0.0", + agents: { researchAgent, analyzerAgent }, +}); + +registry.register(server, { + agentRegistry: voltAgent.agents, + taskStore: customTaskStore, // optional, for resumable tasks +}); + +// Lookup by normalized ID +const meta = registry.getMetadata("my-agents"); + +// JSON-RPC request handling (typically via packages/a2a-server or packages/server-hono) +const result = await server.handleRequest("researchAgent", { + jsonrpc: "2.0", + id: "call-1", + method: "message/send", + params: { + message: { + kind: "message", + role: "user", + messageId: "msg-1", + parts: [ + /* … */ + ], + }, + }, +}); +``` + +## Boundaries + +**Allowed without asking** + +- Adding internal registry helpers. +- Optimizing lookup. +- Improving ID normalization. +- Focused unit tests. + +**Ask first** + +- Changing `A2AServerLike` interface shape. +- Modifying metadata resolution logic. +- Upgrading the A2A spec version (coordinate with `a2a-server`). + +**Never without explicit ask** + +- Removing `A2AServerRegistry` from public exports. +- Changing the ID dedup algorithm. +- Introducing a stateful global singleton registry. + +## Tests + +There are no spec files in this directory yet; coverage is delegated to `packages/a2a-server` and adapter integrations. When adding behavior here, add a `*.spec.ts` neighbor. + +```bash +pnpm --dir packages/core test +pnpm --dir packages/core typecheck +pnpm --dir packages/a2a-server test +``` + +## Related + +- Parents: [`../../AGENTS.md`](../../AGENTS.md) (package), [`../../../../AGENTS.md`](../../../../AGENTS.md) (repo) +- Siblings: [`../agent/AGENTS.md`](../agent/AGENTS.md), [`../workflow/AGENTS.md`](../workflow/AGENTS.md), [`../memory/AGENTS.md`](../memory/AGENTS.md), [`../workspace/AGENTS.md`](../workspace/AGENTS.md), [`../voltops/AGENTS.md`](../voltops/AGENTS.md), [`../tool/AGENTS.md`](../tool/AGENTS.md), [`../observability/AGENTS.md`](../observability/AGENTS.md), [`../mcp/AGENTS.md`](../mcp/AGENTS.md) +- External: `packages/a2a-server` (A2A server runtime + protocol bridge), `packages/server-hono` (HTTP routes for A2A), `packages/internal/src/a2a/` (base types) diff --git a/packages/core/src/a2a/CLAUDE.md b/packages/core/src/a2a/CLAUDE.md new file mode 120000 index 000000000..47dc3e3d8 --- /dev/null +++ b/packages/core/src/a2a/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file diff --git a/packages/core/src/agent/AGENTS.md b/packages/core/src/agent/AGENTS.md index c8cc248c3..05e673af7 100644 --- a/packages/core/src/agent/AGENTS.md +++ b/packages/core/src/agent/AGENTS.md @@ -1,25 +1,103 @@ # Core Agent Runtime -This directory owns the `Agent` runtime and related execution paths. +Owns the `Agent` class and the four paired execution methods: `generateText` / `streamText` and `generateObject` / `streamObject`. Tools, memory persistence, guardrails, subagents, retries, hooks, and telemetry all interact here. -## Local Rules +## Critical Rules -- Treat streaming and non-streaming paths as paired behavior. When changing one, check the matching generate/stream and text/object paths. -- Tool execution, memory persistence, guardrails, subagents, retries, hooks, and telemetry all interact. Trace the full call flow before making localized edits. -- Preserve abort/cancellation behavior and retry semantics. -- Keep OpenTelemetry attributes useful but avoid sensitive or high-cardinality values. Serialize structured attributes with `safeStringify`. -- Keep public option/result types stable unless the task is explicitly an API change. +- **Streaming and non-streaming are paired.** Any change to `streamText` must be checked against `generateText` (and `streamObject` against `generateObject`). Both paths share `prepareExecution`, guardrails (`createGuardrailPipeline`), tool execution, telemetry, and memory persistence. +- **Trace the full call flow before localized edits.** Order: input middleware → input guardrails → `prepareExecution` → AI SDK call (with LLM retries) → output middleware → output guardrails → memory persist → return. +- **Preserve abort/cancellation and retry semantics.** Honor `options.abortSignal`, throw `AbortError` from `errors/abort-error.ts` on cancel, and keep `onRetry` firing for both `source: "llm"` and `source: "middleware"` retries. +- **Telemetry attributes must be safe and bounded.** Use `safeStringify` for structured values; never set raw user content, secrets, or unbounded collections as attributes. +- **New hooks must thread through both paths.** Add to `hooks/index.ts`, fire from both streaming and non-streaming code, and cover with tests in both modes. +- **Public option/result types are stable.** `GenerateTextOptions`, `StreamTextOptions`, `GenerateObjectOptions`, `StreamObjectOptions`, `AgentHooks`, `AgentOperationOutput` — changes need an explicit API task and a changeset. -## Tests +## Execution Paths + +All four methods share the same pipeline; streaming returns iterables, generate awaits the full result. + +- `generateText` — awaits full response; tool loop runs synchronously +- `streamText` — returns iterables (`textStream`, `fullStream`, `toUIMessageStream`); guardrails composed via `createGuardrailPipeline` from `streaming/guardrail-stream.ts` +- `generateObject` — schema-validated structured output +- `streamObject` — streams partial object state + +Hook order (both modes): `onStart` → `onPrepareMessages` → `onPrepareModelMessages` → `onToolStart` / `onToolEnd` (per call) → `onStepFinish` (per LLM step) → `onRetry` (on retry) → `onEnd` / `onError`. + +## Hooks + +Defined in `hooks/index.ts`. Public `AgentHooks` shape — each receives a single args object; some can return overrides: + +- `onStart` — execution begins +- `onPrepareMessages` — before LLM gets messages; can return `{ messages? }` to override +- `onPrepareModelMessages` — after UI→Model conversion; can return `{ modelMessages? }` to override +- `onToolStart` / `onToolEnd` — around each tool call; `onToolEnd` can return `{ output? }` to replace +- `onToolError` — tool throws; can return `{ output? }` to replace error payload +- `onStepFinish` — fires per LLM step (one or more per generation; useful for tracing/usage tracking) +- `onRetry` — LLM or middleware retry +- `onFallback` — fallback model invoked +- `onHandoff` / `onHandoffComplete` — subagent delegation; can `bail(result?)` to skip the supervisor +- `onEnd` / `onError` — execution completes or throws + +```ts +import { createHooks } from "@voltagent/core"; +import { safeStringify } from "@voltagent/internal"; + +const hooks = createHooks({ + onToolStart: async ({ tool, args }) => logger.info(`[${tool.name}]`, safeStringify(args)), + onToolEnd: async ({ tool, output }) => logger.info(`[${tool.name}] →`, safeStringify(output)), +}); +``` + +## Tools, Memory, Subagents, Guardrails + +- **Tools** registered via `options.tools: (Tool | Toolkit)[]` (standalone tools and toolkits both accepted). Lookup and schema validation live in `src/tool/manager/`; execution wrap and span creation (`tool.execution:`) live in `agent.ts`. Optional `toolRouting` injects `searchTools` / `callTool` meta-tools. +- **Memory** persistence is async and non-blocking. `ConversationBuffer` accumulates UI messages during the request; `memory-persist-queue.ts` flushes to `Memory` (`mode: "step"` default, `debounceMs` configurable). See [`../memory/AGENTS.md`](../memory/AGENTS.md). +- **Subagents** declared in `options.subAgents`. The supervisor gets a single shared `delegate_task` tool (defined in `subagent/index.ts`); results are wrapped in OTel spans with parent/child link. Renaming `delegate_task` is a breaking wire change. +- **Guardrails**: input guardrails throw or return transformed input; output guardrails return `{ ok: true | false, reason? }` and may trigger middleware retry. Output guardrails for streams must implement `streamHandler` for chunked processing. + +## Telemetry -- Add focused regression tests near the behavior changed. -- Use `.spec-d.ts` tests for public type inference or generic changes. -- Existing broad coverage lives in `agent.spec.ts`; prefer smaller neighboring specs when the behavior has a dedicated file. +Set attributes via `span.setAttribute(key, safeStringify(value))`. Span name conventions: -Useful commands: +- **Root agent span** is the agent's own `name` (constructed in `AgentTraceContext`, see `open-telemetry/trace-context.ts`). Subagent spans are prefixed `subagent:`. +- **Tool spans**: `tool.execution:` (`agent.ts` createChildSpan call). Downstream exporters parse this prefix. +- **Workflow step spans** (when triggered from a workflow): `workflow.step.` (note: step _type_, not step ID). +- `agent.state` attribute values: `"running"`, `"completed"`, `"cancelled"`. There is also `agent.stateSnapshot` (full state dump via `safeStringify`). + +Avoid: raw conversation content, secrets, high-cardinality user IDs, unbounded collections. + +## Boundaries + +**Allowed without asking** + +- Adding focused `*.spec.ts` near a behavior change. +- Internal refactors that preserve exported types. +- New tests for guardrails / tools / streaming. + +**Ask first** + +- Adding a new hook (must thread through all four methods + tests). +- Changing public option/result types. +- Modifying telemetry attribute keys (downstream consumers parse these). +- Changing memory persistence defaults (`mode`, `debounceMs`). + +**Never without explicit ask** + +- Changing retry/abort semantics. +- Removing `safeStringify` from telemetry. +- Swallowing errors that previously surfaced. + +## Tests + +- Broad coverage: `agent.spec.ts` (5k+ lines). Prefer smaller neighboring specs when the behavior has a dedicated file. +- Type inference: `agent.spec-d.ts`. +- Focused: `guardrail.spec.ts`, `conversation-buffer.spec.ts`, `memory-persist-queue.spec.ts`, `memory-persistence.integration.spec.ts`, `agent-observability.spec.ts`, `streaming/guardrail-stream.spec.ts`. ```bash -pnpm --dir packages/core test:single -- src/agent/path-to-test.spec.ts -pnpm --dir packages/core test +pnpm --dir packages/core test:single -- src/agent/agent.spec.ts pnpm --dir packages/core typecheck ``` + +## Related + +- Parents: [`../../AGENTS.md`](../../AGENTS.md) (package), [`../../../../AGENTS.md`](../../../../AGENTS.md) (repo) +- Siblings: [`../workflow/AGENTS.md`](../workflow/AGENTS.md), [`../memory/AGENTS.md`](../memory/AGENTS.md), [`../workspace/AGENTS.md`](../workspace/AGENTS.md), [`../voltops/AGENTS.md`](../voltops/AGENTS.md), [`../tool/AGENTS.md`](../tool/AGENTS.md), [`../observability/AGENTS.md`](../observability/AGENTS.md), [`../mcp/AGENTS.md`](../mcp/AGENTS.md), [`../a2a/AGENTS.md`](../a2a/AGENTS.md) diff --git a/packages/core/src/mcp/AGENTS.md b/packages/core/src/mcp/AGENTS.md new file mode 100644 index 000000000..294e9adb7 --- /dev/null +++ b/packages/core/src/mcp/AGENTS.md @@ -0,0 +1,116 @@ +# Core MCP Integration + +In-process bridge for the Model Context Protocol: exposes VoltAgent capabilities (tools, resources, prompts) to external MCP clients, and consumes external MCP servers as agent tools. Wire-compatible via the official `@modelcontextprotocol/sdk`. + +## Critical Rules + +- **Preserve MCP protocol wire compatibility.** SDK (`@modelcontextprotocol/sdk`) is range-pinned (caret) in `packages/core/package.json`; bump deliberately and verify against the spec when upgrading. Schema changes, transport additions, or tool result shape changes must not drift from the spec. +- **JSON Schema ↔ Zod conversion is lossy by design.** Use `zod-from-json-schema` (v4) or `zod-from-json-schema-v3` (v3); branch on `"toJSONSchema" in z`. Primitives are lossless; complex constraints (regex patterns, type-mismatched min/max) may downgrade. Document lossy paths in comments. +- **Don't expose internal types over MCP.** Only public `Tool` types, `MCPServerConfig`, `MCPClientConfig`, and types from `src/types.ts` cross the wire. Internal helpers stay internal. +- **`safeStringify` for telemetry of MCP messages.** Import from `@voltagent/internal`. Apply to tool calls, results, and authorization context in logs / spans. +- **Transport security is process-wide.** Stdio inherits parent process env and privileges. SSE / HTTP / streamable-HTTP require explicit `requestInit` with auth headers — never embed credentials in connection strings. +- **Elicitation handlers are once-only by default.** `UserInputBridge.once()` auto-deregisters after first use; permanent handlers persist until `removeHandler()`. Temporary option handlers take precedence over permanent ones. +- **Authorization gates discovery AND execution.** `filterOnDiscovery` hides unauthorized tools from `tools/list`; `checkOnExecution` (default true) re-checks before `tools/call`. Use both for full lockdown. + +## Tool Conversion (JSON Schema → Zod) + +On `tools/list`, the client converts each tool's `inputSchema`: + +1. Fetch tool list from server. +2. Branch by Zod version (`"toJSONSchema" in z`). +3. Convert via `zod-from-json-schema` / `zod-from-json-schema-v3`. +4. Wrap with `createTool()` using namespaced name `_`. +5. `execute()` invokes `MCPClient.callTool()` and handles elicitation responses. + +Conversion errors are logged but discovery continues — resilience over strictness. + +## Transports + +- **stdio** (`type: "stdio"`) — child process over stdin/stdout; inherits env. Local servers. +- **sse** (`type: "sse"`) — Server-Sent Events; stateless. Requires `requestInit` for auth. +- **http** (`type: "http"`) — auto-falls back to SSE if streamable-HTTP unsupported. +- **streamable-http** (`type: "streamable-http"`) — persistent streaming HTTP; no fallback. + +All accept optional `timeout` (default 60000 ms — `DEFAULT_REQUEST_TIMEOUT_MSEC` in the MCP SDK). + +## Authorization + +```ts +const can: MCPCanFunction = async ({ toolName, action, userId, context }) => { + if (toolName === "delete_database" && action === "execution") { + return userId === "admin" ? true : { allowed: false, reason: "Admins only" }; + } + return true; +}; + +const mcp = new MCPConfiguration({ + servers: { external: { type: "http", url: "..." } }, + authorization: { can, filterOnDiscovery: true, checkOnExecution: true }, +}); + +// Context flows from agent OperationContext or explicit MCPAuthorizationContext +const tools = await mcp.getTools({ userId: "user123", context: { roles: ["viewer"] } }); +``` + +## Code Example + +```ts +import { MCPConfiguration } from "@voltagent/core"; + +const mcp = new MCPConfiguration({ + servers: { + weather: { type: "stdio", command: "node", args: ["./weather-server.js"] }, + github: { + type: "http", + url: "http://localhost:3000", + requestInit: { headers: { Authorization: "Bearer token_here" } }, + }, + }, + authorization: { + can: async ({ toolName, action, userId }) => + action === "execution" && toolName.startsWith("delete") ? userId === "admin" : true, + checkOnExecution: true, + }, +}); + +const tools = await mcp.getTools({ userId: "user1" }); +const agent = new Agent({ model: "claude-3-5-sonnet-20241022", tools }); +``` + +## Boundaries + +**Allowed without asking** + +- Adding transport fallback logic. +- Refining Zod conversion fallbacks. +- Expanding authorization checks. +- Adding tests. + +**Ask first** + +- Upgrading the MCP SDK version. +- Modifying `MCPClient.callTool()` signature. +- Changing tool naming scheme (`_`). +- Adding new transport types. + +**Never without explicit ask** + +- Removing `safeStringify` from telemetry. +- Exporting internal helper types. +- Changing authorization context shape. +- Hand-editing tool definitions for security. + +## Tests + +```bash +pnpm --dir packages/core test:single -- src/mcp/client/index.spec.ts +pnpm --dir packages/core test:single -- src/mcp/registry/index.spec.ts +pnpm --dir packages/core test +pnpm --dir packages/core typecheck +``` + +## Related + +- Parents: [`../../AGENTS.md`](../../AGENTS.md) (package), [`../../../../AGENTS.md`](../../../../AGENTS.md) (repo) +- Siblings: [`../agent/AGENTS.md`](../agent/AGENTS.md), [`../workflow/AGENTS.md`](../workflow/AGENTS.md), [`../memory/AGENTS.md`](../memory/AGENTS.md), [`../workspace/AGENTS.md`](../workspace/AGENTS.md), [`../voltops/AGENTS.md`](../voltops/AGENTS.md), [`../tool/AGENTS.md`](../tool/AGENTS.md), [`../observability/AGENTS.md`](../observability/AGENTS.md), [`../a2a/AGENTS.md`](../a2a/AGENTS.md) +- External: `packages/mcp-server` (standalone MCP server), `packages/docs-mcp` (docs-as-MCP) diff --git a/packages/core/src/mcp/CLAUDE.md b/packages/core/src/mcp/CLAUDE.md new file mode 120000 index 000000000..47dc3e3d8 --- /dev/null +++ b/packages/core/src/mcp/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file diff --git a/packages/core/src/memory/AGENTS.md b/packages/core/src/memory/AGENTS.md index dfce73719..1341dffef 100644 --- a/packages/core/src/memory/AGENTS.md +++ b/packages/core/src/memory/AGENTS.md @@ -1,24 +1,156 @@ # Core Memory -This directory defines shared memory types, manager behavior, semantic search, working memory, and adapter contracts. +Shared memory contracts, types, and orchestration for conversations, working memory, semantic search, and workflow state. External adapter packages (`postgres`, `libsql`, `supabase`, `cloudflare-d1`, `voltagent-memory`) implement these contracts. -## Local Rules +## Critical Rules -- Preserve `StorageAdapter`, `VectorAdapter`, `EmbeddingAdapter`, conversation, message, working-memory, and workflow-state contracts. -- Shared behavior here should remain compatible with external adapters such as Postgres, LibSQL, Supabase, Cloudflare D1, and managed memory. -- Use `safeStringify` for persisted structured data and logs. -- Be careful with message shape compatibility. Agent persistence, workflow state, server APIs, and examples can depend on these types. -- Keep adapter-neutral logic in core; adapter-specific SQL or platform behavior belongs in adapter packages. +- **Adapter contracts are cross-package API.** `StorageAdapter`, `VectorAdapter`, `EmbeddingAdapter` signature changes break every external adapter. Coordinate updates and add a changeset for each affected package. +- **Message and Conversation shapes are load-bearing.** `UIMessage`, `Conversation`, `ConversationStepRecord`, `WorkflowStateEntry` are persisted by adapters and consumed by agents, workflows, server APIs, and the SDK. Treat as wire-compatible. +- **Adapter-neutral logic only.** Platform-specific SQL, HTTP, or cloud SDK code belongs in the adapter package, not here. +- **`safeStringify` for persistence and logs.** Import from `@voltagent/internal`. +- **Type inference is API.** Add `index.spec-d.ts` coverage when changing public memory generics or adapter generics. -## Tests +## Adapter Contracts + +The interfaces below are **partial summaries** of the load-bearing methods. Read `memory/types.ts` for the canonical signatures (queryConversations variants, workflow run queries, etc.). + +```ts +// StorageAdapter — implemented by postgres / libsql / supabase / cloudflare-d1 / voltagent-memory +interface StorageAdapter { + // Messages + addMessage(message: UIMessage, userId, conversationId, context?): Promise; + addMessages(messages: UIMessage[], userId, conversationId, context?): Promise; + getMessages( + userId, + conversationId, + options?, + context? + ): Promise[]>; + deleteMessages(messageIds: string[], userId, conversationId, context?): Promise; + clearMessages(userId, conversationId?, context?): Promise; + + // Conversations + createConversation(input: CreateConversationInput): Promise; + getConversation(id): Promise; + queryConversations(options): Promise; + updateConversation(id, updates): Promise; + deleteConversation(id): Promise; + + // Working memory + workflow state + getWorkingMemory(params): Promise; + setWorkingMemory(params): Promise; + deleteWorkingMemory(params): Promise; + getWorkflowState(executionId): Promise; + setWorkflowState(executionId, state): Promise; + updateWorkflowState(executionId, updates): Promise; + getSuspendedWorkflowStates(workflowId?): Promise; + // … plus query helpers (queryWorkflowRuns, getConversations, countConversations, etc.) — see types.ts + + // Optional: detailed tracing + saveConversationSteps?(steps: ConversationStepRecord[]): Promise; + getConversationSteps?(userId, conversationId, options?): Promise; +} + +interface VectorAdapter { + store(id: string, vector: number[], metadata?): Promise; + storeBatch(items: VectorItem[]): Promise; + search(vector: number[], options?: { limit?; threshold?; filter? }): Promise; + delete(id: string): Promise; + deleteBatch(ids: string[]): Promise; + clear(): Promise; +} + +interface EmbeddingAdapter { + embed(text: string): Promise; + embedBatch(texts: string[]): Promise; + getDimensions(): number; + getModelName(): string; +} +``` -- Add shared contract tests when manager behavior changes. -- Add type tests for public memory API inference or type compatibility changes. +## External Adapter Packages + +Cross-check these whenever you change a contract above: + +- `packages/postgres` — Postgres + pgvector +- `packages/libsql` — LibSQL / Turso +- `packages/supabase` — Supabase (pgvector) +- `packages/cloudflare-d1` — Cloudflare D1 (SQLite) +- `packages/voltagent-memory` — managed VoltOps memory + +## Memory Orchestration + +`Memory` (in `index.ts`) owns the storage adapter and optional embedding + vector adapters. When all three are configured, messages are auto-embedded on save and `getMessagesWithSemanticSearch()` merges recent + similar messages. + +```ts +const memory = new Memory({ + storage: new InMemoryStorageAdapter(), + embedding: new AiSdkEmbeddingAdapter("openai/text-embedding-3-small"), + vector: new InMemoryVectorAdapter(), + enableCache: true, +}); + +await memory.addMessage(msg, userId, conversationId); + +const merged = await memory.getMessagesWithSemanticSearch( + userId, + conversationId, + "tell me about workflows", + { limit: 10, semanticLimit: 5, mergeStrategy: "interleave" } +); +``` -Useful commands: +`MemoryManager` (in `manager/`) wraps `Memory` for use by agents — it injects `OperationContext`, defaults to an in-memory adapter when none supplied, and runs background persistence. + +## Working Memory + +Short-term scratchpad keyed by `{ scope, conversationId?, userId }`. Scope is `"conversation"` (per-conversation) or `"user"` (across user). Update modes: `"replace"` (default) or `"append"`. + +```ts +await memory.updateWorkingMemory({ + conversationId, + userId, + content: "User prefers brief responses", + mode: "replace", +}); +``` + +## Boundaries + +**Allowed without asking** + +- Adding optional fields to types. +- Adding new optional adapter methods (with default implementations). +- New convenience query methods. +- Internal Memory orchestration changes that preserve public behavior. + +**Ask first** + +- Adding required fields to `ConversationStepRecord` / `WorkflowStateEntry`. +- Restructuring `getMessages()` options. +- Changing cache invalidation or embedding batch behavior. +- Changing default merge strategies. + +**Never without explicit ask** + +- Changing `StorageAdapter` / `VectorAdapter` / `EmbeddingAdapter` method signatures. +- Breaking `UIMessage` / `Conversation` shape. +- Adding platform-specific code (SQL, HTTP) to core. +- Removing default in-memory adapter behavior. + +## Tests ```bash -pnpm --dir packages/core test:single -- src/memory/path-to-test.spec.ts -pnpm --dir packages/core test +pnpm --dir packages/core test:single -- src/memory/index.spec-d.ts +pnpm --dir packages/core test:single -- src/memory/semantic-search.spec.ts +pnpm --dir packages/core test:single -- src/memory/working-memory.spec.ts pnpm --dir packages/core typecheck ``` + +When changing adapter contracts, run the matching adapter package tests too (e.g., `pnpm --dir packages/postgres test`). + +## Related + +- Parents: [`../../AGENTS.md`](../../AGENTS.md) (package), [`../../../../AGENTS.md`](../../../../AGENTS.md) (repo) +- Siblings: [`../agent/AGENTS.md`](../agent/AGENTS.md), [`../workflow/AGENTS.md`](../workflow/AGENTS.md), [`../workspace/AGENTS.md`](../workspace/AGENTS.md), [`../voltops/AGENTS.md`](../voltops/AGENTS.md), [`../tool/AGENTS.md`](../tool/AGENTS.md), [`../observability/AGENTS.md`](../observability/AGENTS.md), [`../mcp/AGENTS.md`](../mcp/AGENTS.md), [`../a2a/AGENTS.md`](../a2a/AGENTS.md) +- External adapter packages: `packages/postgres`, `packages/libsql`, `packages/supabase`, `packages/cloudflare-d1`, `packages/voltagent-memory` diff --git a/packages/core/src/observability/AGENTS.md b/packages/core/src/observability/AGENTS.md new file mode 100644 index 000000000..8cc8a0d00 --- /dev/null +++ b/packages/core/src/observability/AGENTS.md @@ -0,0 +1,97 @@ +# Core Observability + +Owns OpenTelemetry trace context, span helpers, attribute key conventions, span/log processors, and storage adapters. Span names and attribute keys here are **wire contracts** consumed by every other subsystem and by the external exporter packages (`langfuse-exporter`, `vercel-ai-exporter`). + +## Critical Rules + +- **`safeStringify` for all structured attributes.** Import from `@voltagent/internal`. Attribute values must be scalars or pre-serialized strings. +- **No raw user content, secrets, or PII in attributes.** Conversation/user IDs are allowed (lookup keys); raw messages, emails, API keys, file contents are not. +- **Span names are wire contracts.** The agent root span uses the agent's own `name` (set by `AgentTraceContext`). Tool spans are `tool.execution:`. Workflow step spans are `workflow.step.`. These are parsed by exporters and dashboards — renaming breaks production. +- **Attribute keys follow `..` namespace.** Examples: `agent.state`, `workflow.step.index`, `tool.name`, `model.name`, `vector.operation`. Don't introduce new top-level subsystem namespaces without coordination. +- **Low cardinality only.** Fixed enums and bounded counters are safe. Avoid unbounded arrays, dynamic tag generation, or repeated user inputs as attribute values. +- **Context propagation must be explicit.** `AgentTraceContext` and `WorkflowTraceContext` carry parent/child links and inherited attributes across async boundaries. Don't break the propagation chain. +- **Sampling, batching, and export defaults are production-critical.** Coordinate any changes — they ripple to serverless and node runtimes. + +## Span Attribute Reference + +**Entity & context** — `entity.id`, `entity.type` (`"agent"` \| `"workflow"`), `entity.name`, `user.id`, `conversation.id`, `operation.id` (unique per invocation). + +**Agent** — root span name = the agent's own `name`. Subagents are prefixed `subagent:`. Attributes: `agent.state` (`"running"` / `"completed"` / `"cancelled"`), `agent.stateSnapshot` (full state via `safeStringify`). + +**Workflow** — span name: `workflow.step.` (the step _type_, not the step ID). Attributes: `workflow.execution.id`, `workflow.step.index`, `workflow.step.type`, `workflow.step.name`, `workflow.replayed` (bool), `workflow.replay.source_*`. + +**Tool** — span name: `tool.execution:`. Attribute: `tool.name`. (Additional tool attributes are not currently set automatically — if you add input/output, route through `safeStringify` and treat the keys as new wire contracts.) + +**Model & usage** — `model.name`; counters `usage.prompt_tokens`, `usage.completion_tokens`, `usage.total_tokens`. + +**Vector / memory** — `vector.operation`, `vector.query`, `vector.adapter`, `vector.results_count`. + +**Workspace** — `workspace.fs.path`, `workspace.fs.bytes`, `workspace.sandbox.exit_code`, `workspace.search.mode`, `workspace.search.top_k`, `workspace.search.results`. + +**Generic I/O fallback** — `input`, `output` (Langfuse exporter looks for these; safe if `safeStringify`'d). + +## Code Examples + +```ts +// Good — bounded scalars + safeStringify for structured values +import { safeStringify } from "@voltagent/internal"; +import { SpanStatusCode } from "@opentelemetry/api"; + +span.setAttributes({ + "entity.id": agentId, // scalar + "entity.type": "agent", // fixed enum + "usage.prompt_tokens": 150, // counter + "agent.state": "running", // fixed enum: running | completed | cancelled + input: safeStringify(messagesArray), // structured → string + metadata: safeStringify({ retries: 2 }), +}); +span.setStatus({ code: SpanStatusCode.OK }); +span.end(); + +// Bad — raw JSON.stringify in production telemetry +span.setAttribute("input", JSON.stringify(messages)); + +// Bad — PII / secrets / unbounded values +span.setAttribute("email", user.email); +span.setAttribute("api_key", SECRET_KEY); +span.setAttribute("conversation", userInput); // raw, unbounded +span.setAttribute("session_ids", userIds); // unbounded array +``` + +## Boundaries + +**Allowed without asking** + +- Adding span helpers for a new subsystem. +- Adding new workspace attributes if documented in `types.ts`. +- Internal refactors to processors / storage adapters that preserve span output. +- New tests for sampling / filtering / export. + +**Ask first** + +- Renaming any span name (e.g., `tool.execution:` → something else). +- Adding / removing / renaming top-level attribute namespaces (`agent.*`, `tool.*`, `workflow.*`, `model.*`). +- Changing `SpanAttributes` interface in `types.ts`. +- Modifying `OperationContext` shape (defined in `agent/types.ts`, consumed by every subsystem). +- Changing default sampling / batch size / export timeout. + +**Never without explicit ask** + +- Removing `safeStringify` from production telemetry paths. +- Hardcoding secrets / raw conversation / unbounded collections into attributes. +- Disabling span filtering or exporter integration without a migration plan. + +## Tests + +```bash +pnpm --dir packages/core test:single -- src/observability/processors/span-filter-processor.spec.ts +pnpm --dir packages/core test +pnpm --dir packages/core typecheck +``` + +## Related + +- Parents: [`../../AGENTS.md`](../../AGENTS.md) (package), [`../../../../AGENTS.md`](../../../../AGENTS.md) (repo) +- Siblings: [`../agent/AGENTS.md`](../agent/AGENTS.md), [`../workflow/AGENTS.md`](../workflow/AGENTS.md), [`../memory/AGENTS.md`](../memory/AGENTS.md), [`../workspace/AGENTS.md`](../workspace/AGENTS.md), [`../voltops/AGENTS.md`](../voltops/AGENTS.md), [`../tool/AGENTS.md`](../tool/AGENTS.md), [`../mcp/AGENTS.md`](../mcp/AGENTS.md), [`../a2a/AGENTS.md`](../a2a/AGENTS.md) +- Telemetry exporters: `packages/langfuse-exporter`, `packages/vercel-ai-exporter` +- External: [OTel semantic conventions](https://opentelemetry.io/docs/specs/semconv/) diff --git a/packages/core/src/observability/CLAUDE.md b/packages/core/src/observability/CLAUDE.md new file mode 120000 index 000000000..47dc3e3d8 --- /dev/null +++ b/packages/core/src/observability/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file diff --git a/packages/core/src/tool/AGENTS.md b/packages/core/src/tool/AGENTS.md new file mode 100644 index 000000000..db5b972d2 --- /dev/null +++ b/packages/core/src/tool/AGENTS.md @@ -0,0 +1,137 @@ +# Core Tools + +Owns tool definition (`Tool` / `Toolkit`), schema validation (Zod), the tool manager (registration, lookup, execution), tool routing (`searchTools` / `callTool` meta-tools), span tracking, and error normalization. Every agent tool call flows through this directory. + +## Critical Rules + +- **Parameters schema is mandatory (Zod).** Every tool requires a `z.object(...)` schema. The AI SDK converts to JSON Schema; coercion at execution via `coerceStringifiedJsonToolArgs()`. +- **Tool names must be unique.** Uniqueness is enforced across standalone tools and toolkits in `ToolManager`. Duplicates warn but the last one wins; tool spans collide on duplicates. +- **Span name convention is `tool.execution:`** (created in `agent.ts` via `oc.traceContext.createChildSpan`). Downstream telemetry (Langfuse, Vercel AI exporter, dashboards) parses this prefix — renaming breaks production observability. +- **`safeStringify` for tool args/output in spans.** Import from `@voltagent/internal`. Args/output may contain secrets; never `JSON.stringify` directly into attributes. +- **Errors flow through `buildToolErrorResult()` and `onToolError`.** Tool throws → `onToolError` hook (can override output) → normalized to `{ error: string; ... }` for the LLM. +- **Toolkit `addInstructions: true` merges into the agent system prompt.** Avoids per-tool instruction duplication; respects agent-level instruction overrides. + +## Tool Definition + +```ts +import { z } from "zod"; +import { createTool } from "@voltagent/core"; + +const calculate = createTool({ + name: "calculate", + description: "Perform basic arithmetic.", + parameters: z.object({ + operation: z.enum(["add", "subtract", "multiply", "divide"]), + a: z.number(), + b: z.number(), + }), + outputSchema: z.object({ + result: z.number(), + explanation: z.string(), + }), + execute: async ({ operation, a, b }) => { + const ops = { add: a + b, subtract: a - b, multiply: a * b, divide: a / b }; + return { result: ops[operation], explanation: `${a} ${operation} ${b}` }; + }, +}); +``` + +Client-side tools (no `execute`) are exposed to the LLM but not executed server-side. + +## Toolkit Grouping + +```ts +const fileOps = createToolkit({ + name: "file_operations", + description: "Read and write files.", + instructions: "Always check permissions before write operations.", + addInstructions: true, // merges into the agent's system prompt + tools: [readFile, writeFile, deleteFile], +}); + +const agent = new Agent({ tools: [fileOps, calculate] }); +``` + +## Tool Manager & Execution + +`ToolManager` handles registration (standalone + toolkits) and prepares tools for the AI SDK. Execution wrap (in `agent.ts` via `createToolExecuteFunction()`): + +1. `onToolStart` hook fires. +2. Schema validation + `coerceStringifiedJsonToolArgs()`. +3. Span created (`tool.execution:`); `tool.name` attribute is set on the span. +4. `execute()` runs. +5. `onToolEnd` (success) or `onToolError` (failure) fires. +6. Errors normalized via `buildToolErrorResult()` → returned to the LLM. + +## Tool Routing (Search + Call meta-tools) + +```ts +const agent = new Agent({ + tools: [tool1, tool2, tool3], + toolRouting: { + pool: [tool1, tool2, tool3], + expose: [tool1, tool2], // LLM only sees these + embedding: embeddingModel, + topK: 3, + enforceSearchBeforeCall: false, + }, +}); +``` + +Two meta-tools (`searchTools`, `callTool`) are auto-injected. The embedding strategy caches tool embeddings; search spans are named `tool.search.embedding:` with cache hit/miss attributes. + +## Code Examples + +```ts +// Good — set tool.name on the span; serialize structured values with safeStringify +import { safeStringify } from "@voltagent/internal"; +span.setAttribute("tool.name", tool.name); +// If you add more attributes, run them through safeStringify first +span.setAttribute("tool.metadata", safeStringify({ retries: 0 })); + +// Bad — raw JSON.stringify (may leak structure of secrets) +span.setAttribute("tool.metadata", JSON.stringify(args)); + +// Bad — no schema (createTool throws) +createTool({ name: "x", description: "x", parameters: undefined, execute: async () => "ok" }); +``` + +## Boundaries + +**Allowed without asking** + +- Adding focused tool tests. +- Refactoring tool internals. +- New routing strategies. +- Additional `ToolHooks`. +- New client-side tools. + +**Ask first** + +- Changing `Tool` / `Toolkit` public types. +- Renaming meta-tool names (`searchTools`, `callTool`). +- Modifying span attribute keys. +- Changing error normalization shape. +- Altering schema coercion behavior. + +**Never without explicit ask** + +- Removing `safeStringify` from telemetry. +- Bypassing `ToolManager` uniqueness checks. +- Swallowing errors before `onToolError` hook fires. +- Removing tool exports from `packages/core/src/index.ts`. + +## Tests + +```bash +pnpm --dir packages/core test:single -- src/tool/index.spec.ts +pnpm --dir packages/core test:single -- src/tool/manager/index.spec.ts +pnpm --dir packages/core test +pnpm --dir packages/core typecheck +``` + +## Related + +- Parents: [`../../AGENTS.md`](../../AGENTS.md) (package), [`../../../../AGENTS.md`](../../../../AGENTS.md) (repo) +- Siblings: [`../agent/AGENTS.md`](../agent/AGENTS.md), [`../workflow/AGENTS.md`](../workflow/AGENTS.md), [`../memory/AGENTS.md`](../memory/AGENTS.md), [`../workspace/AGENTS.md`](../workspace/AGENTS.md), [`../voltops/AGENTS.md`](../voltops/AGENTS.md), [`../observability/AGENTS.md`](../observability/AGENTS.md), [`../mcp/AGENTS.md`](../mcp/AGENTS.md), [`../a2a/AGENTS.md`](../a2a/AGENTS.md) +- External: `@voltagent/internal` (`safeStringify`) diff --git a/packages/core/src/tool/CLAUDE.md b/packages/core/src/tool/CLAUDE.md new file mode 120000 index 000000000..47dc3e3d8 --- /dev/null +++ b/packages/core/src/tool/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file diff --git a/packages/core/src/voltops/AGENTS.md b/packages/core/src/voltops/AGENTS.md index 9c2ba0cfc..61ec11700 100644 --- a/packages/core/src/voltops/AGENTS.md +++ b/packages/core/src/voltops/AGENTS.md @@ -1,21 +1,131 @@ # Core VoltOps Integration -This directory owns VoltOps clients, prompt APIs, prompt management, action clients, local prompt loading, and related types. +Unified client for the VoltOps platform: prompt management (remote + local + templating), managed memory APIs, action execution, observability, and feedback. Wire compatibility with the VoltOps API is paramount. -## Local Rules +## Critical Rules -- Preserve wire compatibility with VoltOps APIs. Be cautious with request/response shapes, endpoint paths, auth headers, and error handling. -- Use `safeStringify` for request bodies and structured logging/telemetry. -- Keep prompt template behavior deterministic and covered by tests. -- Avoid changing generated or API-shaped client methods without checking the matching tests and exported types. -- Be careful with global client behavior and precedence rules; existing priority tests document expected selection. +- **Wire compatibility is non-negotiable.** Endpoint paths, HTTP methods, request/response field names, and exported types are part of a contract with deployed VoltOps consumers (agents, dashboards, integrations). Renames and removals are breaking changes. +- **Auth headers are fixed.** Every request sends `X-Public-Key` and `X-Secret-Key` (from `options.publicKey` / `options.secretKey` or env `VOLTAGENT_PUBLIC_KEY` / `VOLTAGENT_SECRET_KEY`). Renaming headers or env vars breaks existing setups. +- **`safeStringify` for all request bodies and structured logs.** Import from `@voltagent/internal`. +- **Prompt precedence is strict.** Local → per-agent VoltOpsClient → global VoltOpsClient → fallback instructions. Tests in `client-priority.spec.ts` document expected selection. +- **Template rendering must stay deterministic.** `{{ variable }}` substitution via the in-house template engine — no extra dependencies, no eval-style behavior. +- **Do not remove exported types.** External callers depend on `PromptReference`, `PromptContent`, `VoltOpsClientOptions`, action param types, and result types. -## Tests +## Endpoints + +### Prompt API + +- `GET /prompts/public/{name}` — query params: `version`, `label` (default `latest`); returns `{ type: "text" | "chat", text?, messages? }` + +### Managed Memory (`/managed-memory/projects/databases/{databaseId}/…`) + +- `messages` — `add`, `addBatch`, `list`, `delete`, `clear` +- `conversations` — `create`, `get`, `query`, `update`, `delete` +- `vectors` — `store`, `storeBatch`, `search`, `get`, `delete`, `clear` +- `working-memory` — `get`, `set`, `delete` +- `workflow-states` — `get`, `set`, `list`, `listSuspended` + +### Observability & Feedback + +- `POST /api/public/feedback/tokens` — short-lived UI feedback token +- `GET /api/public/otel/v1/traces` — list traces with filters + +### Actions + +`client.actions` is a namespaced object (see `actions/client.ts` → `VoltOpsActionsClient`). Each provider gets its own sub-namespace; each method takes a typed params object that includes a `credential` (stored `credentialId` or inline) plus operation-specific fields. + +Provider namespaces: `airtable`, `slack`, `discord`, `gmail`, `googleCalendar`, `googleDrive`, `postgres`. + +```ts +await client.actions.slack.postMessage({ + actionId: "act_…", + credential: { credentialId: "cred_123" }, + channel: "C123456", + text: "Hello team", +}); + +await client.actions.airtable.createRecord({ + actionId: "act_…", + credential: { credentialId: "cred_456" }, + baseId: "appXXX", + tableId: "tblYYY", + input: { Name: "Acme" }, +}); +``` + +## Prompt Management + +**Remote** (`prompt-api-client.ts` → `prompt-manager.ts`): `client.prompts.getPrompt(reference)` → cached fetch → template render. Cache: LRU, default 100 entries, 5 min TTL, per-reference key, override via `PromptReference.promptCache`. + +**Local** (`local-prompts.ts`): `.md` files with YAML frontmatter (`type`, `version`, `labels`, `tags`, `config`). Search order: `$VOLTAGENT_PROMPTS_PATH` → `$VOLTAGENT_PROMPTS_DIR` → `.voltagent/prompts/`. Selection: explicit version > label > `latest` label > highest version. Throws `LocalPromptNotFoundError` on miss or path escape. + +**Precedence** (`createPromptHelperWithFallback`): -Add or update focused tests for client requests, prompt manager behavior, template rendering, and priority rules: +1. Local prompts (file-based) +2. Per-agent VoltOpsClient (`new VoltOpsClient({...})`) +3. Global VoltOpsClient (set on `new VoltAgent({ voltOpsClient })`) +4. Fallback instructions (string in agent definition) + +## Auth & Errors + +```ts +const headers = { + "Content-Type": "application/json", + "X-Public-Key": this.options.publicKey ?? "", + "X-Secret-Key": this.options.secretKey ?? "", +}; +const response = await this.fetchImpl(url, { + method, + headers, + body: body !== undefined ? safeStringify(body) : undefined, +}); +if (!response.ok) { + throw new Error(payload?.message ?? `VoltOps request failed (${response.status})`); +} +``` + +No built-in retry — callers implement their own backoff. Errors are logged via the package logger before being thrown. + +## Boundaries + +**Allowed without asking** + +- Adding new optional fields to request/response types. +- Adding new action providers. +- Improving logging or telemetry. +- Adding new prompt cache config options. +- Adding tests. + +**Ask first** + +- Changing template engine or `{{var}}` syntax. +- Changing cache TTL / max-size defaults. +- Changing precedence order. +- Modifying error type hierarchy. +- Removing exported types. + +**Never without explicit ask** + +- Renaming endpoint paths. +- Renaming request/response field names. +- Renaming auth header names or env vars. +- Removing endpoints. +- Breaking precedence semantics. + +## Tests ```bash -pnpm --dir packages/core test:single -- src/voltops/path-to-test.spec.ts +pnpm --dir packages/core test:single -- src/voltops/prompt-manager.spec.ts +pnpm --dir packages/core test:single -- src/voltops/client-priority.spec.ts +pnpm --dir packages/core test:single -- src/voltops/client.spec.ts pnpm --dir packages/core test pnpm --dir packages/core typecheck ``` + +Mock pattern: `vi.mock()` for `fetch` and the logger. Assert request bodies serialized via `safeStringify`. Priority tests verify `createPromptHelperWithFallback()` selects the expected source. + +## Related + +- Parents: [`../../AGENTS.md`](../../AGENTS.md) (package), [`../../../../AGENTS.md`](../../../../AGENTS.md) (repo) +- Siblings: [`../agent/AGENTS.md`](../agent/AGENTS.md), [`../workflow/AGENTS.md`](../workflow/AGENTS.md), [`../memory/AGENTS.md`](../memory/AGENTS.md), [`../workspace/AGENTS.md`](../workspace/AGENTS.md), [`../tool/AGENTS.md`](../tool/AGENTS.md), [`../observability/AGENTS.md`](../observability/AGENTS.md), [`../mcp/AGENTS.md`](../mcp/AGENTS.md), [`../a2a/AGENTS.md`](../a2a/AGENTS.md) +- External packages: `packages/voltagent-memory` (managed memory adapter that hits these endpoints), `packages/sdk` (mirrors public types for external callers) diff --git a/packages/core/src/workflow/AGENTS.md b/packages/core/src/workflow/AGENTS.md index 553b53312..37d15708b 100644 --- a/packages/core/src/workflow/AGENTS.md +++ b/packages/core/src/workflow/AGENTS.md @@ -1,21 +1,148 @@ # Core Workflow Runtime -This directory owns workflow creation, chaining, execution, streaming, suspension, and step helpers. +Owns workflow creation, chaining, execution, streaming, suspension, time travel, and step helpers. Type inference, suspend/resume state, step IDs, hooks, and usage tracking all need to stay consistent. -## Local Rules +## Critical Rules -- Preserve suspend/resume behavior, checkpoint state, step IDs, hooks, time travel, cancellation, and usage tracking. -- Runtime behavior and type inference are both part of the API. Changes to chain generics or step typing usually need `.spec-d.ts` coverage. -- New or changed steps under `steps/` need exports from the local step index and, when public, from workflow/core package exports. -- Keep workflow state serializable and backwards-aware. Use `safeStringify` for production serialization. -- Avoid special-casing a step in the core executor if the behavior belongs in a step helper. +- **Step IDs are checkpoint anchors.** Renaming or reassigning step IDs breaks resume of in-flight workflows and time-travel replay. Treat IDs like database primary keys. +- **Checkpoint format is persisted.** Workflow state is serialized via `safeStringify`. Schema changes to `WorkflowSuspensionMetadata` / `WorkflowStateStore` / `WorkflowCheckpointStepData` break deserialization of stored executions. +- **Type inference is API.** `WorkflowChain` generics propagate output types through `.andThen` → `.andAll` → `.andRace`, etc. Add `*.spec-d.ts` coverage for any chain or step typing change. +- **New / changed steps must be exported** from `steps/index.ts` and, when public, from `src/index.ts` plus `packages/core/src/index.ts`. +- **Step-specific behavior belongs in a step helper**, not in `core.ts` executor logic. Resist special-casing in the executor. +- **Thread `AbortSignal` and `suspendController.signal` through chains.** Breaking the signal chain breaks graceful cancellation and suspension. -## Tests +## Authoring Workflows + +```ts +const workflow = createWorkflow( + { + id: "user-onboarding", + input: z.object({ userId: z.string() }), + result: z.object({ message: z.string() }), + }, + andThen({ + id: "fetch-user", + execute: async ({ data }) => ({ ...data, user: await db.getUser(data.userId) }), + }), + andAgent(({ data }) => `Welcome message for ${data.user.name}`, agent, { + schema: z.object({ message: z.string() }), + }) +); + +const result = await workflow.run({ userId: "123" }); +``` + +## Steps + +To add a new step type: + +1. Create `steps/and-my-step.ts` exporting a factory function with a stable `id`. +2. Export from `steps/index.ts` and add types to `steps/types.ts`. +3. Re-export from `packages/core/src/index.ts` if public. +4. Add tests: `steps/and-my-step.spec.ts` (runtime) and add inference assertions in `chain.spec-d.ts` if generics change. + +Each step receives a `WorkflowExecuteContext` with `{ data, state, workflowState, setWorkflowState, getStepData, getStepResult, getInitData, suspend, bail, abort, resumeData?, retryCount, logger, writer }` (see `internal/types.ts` for canonical shape). Returns the next data value or throws. + +## Suspend / Resume + +```ts +// Inside a step +suspend("waiting-for-approval", { approvalId: "a-123" }); + +// Resume — two equivalent shapes: +// 1. From the result object returned by the original run +const result = await workflow.run(input); +if (result.status === "suspended") { + await result.resume({ approved: true }); +} + +// 2. By executionId (if you only have the ID + an external checkpoint store) +await workflow.run(input, { + resumeFrom: { executionId, resumeStepIndex, resumeData: { approved: true } }, +}); +``` + +`WorkflowSuspensionMetadata.checkpoint` captures `{ stepExecutionState, completedStepsData, workflowState, stepData, usage }`. Always serialize with `safeStringify` for telemetry/persistence: + +```ts +import { safeStringify } from "@voltagent/internal"; +span.setAttribute("workflow.checkpoint", safeStringify(checkpoint)); +``` + +## Time Travel + +`workflow.timeTravel({ executionId, stepId, inputData?, resumeData?, workflowStateOverride?, memory? })` replays from a prior step in a previous execution with a new execution ID. Original state is loaded, prior step outputs restored up to `stepId`, then execution continues normally. -Use targeted runtime tests for changed behavior and type tests for inference changes: +## Hooks & Usage + +Hooks: `onStart`, `onStepStart`, `onStepEnd`, `onSuspend`, `onError`, `onFinish`, `onEnd`. Terminal hooks receive `WorkflowHookContext` (status, result, error, suspension/cancellation metadata, per-step snapshots). + +Usage (tokens, cost) is accumulated across `andAgent` steps and surfaces in `result.usage` and `WorkflowSuspensionMetadata.checkpoint.usage`. + +## Type Inference Pattern + +```ts +// chain.spec-d.ts +const workflow = createWorkflowChain({ + id: "test", + input: z.object({ userId: z.string() }), + result: z.object({ name: z.string() }), +}) + .andThen({ + id: "load", + execute: async ({ data }) => { + expectTypeOf(data).toEqualTypeOf<{ userId: string }>(); + return { ...data, name: "Alice" }; + }, + }) + .andThen({ + id: "use", + execute: async ({ data }) => { + expectTypeOf(data).toEqualTypeOf<{ userId: string; name: string }>(); + return { name: data.name }; + }, + }); +``` + +Add `*.spec-d.ts` cases when changing chain generics, schema-narrowed outputs, or `andAll` / `andRace` array typing. + +## Boundaries + +**Allowed without asking** + +- Adding new step types. +- New hook handlers. +- New utility helpers in `internal/`. +- New tests. +- Telemetry attributes (using `safeStringify`). + +**Ask first** + +- Chain generic changes (`WorkflowChain`). +- New schema fields on `WorkflowStepData`. +- Hook signature changes. +- Modifying suspend/resume protocol. + +**Never without explicit ask** + +- Renaming or removing chain methods. +- Changing checkpoint shape in a non-backwards-compatible way. +- Removing step ID stability guarantees. +- Special-casing a step in `core.ts` instead of a step helper. + +## Tests ```bash -pnpm --dir packages/core test:single -- src/workflow/path-to-test.spec.ts -pnpm --dir packages/core test -pnpm --dir packages/core typecheck +pnpm --dir packages/core test:single -- src/workflow/core.spec.ts +pnpm --dir packages/core test:single -- src/workflow/suspend-resume.spec.ts +pnpm --dir packages/core test:single -- src/workflow/time-travel.spec.ts +pnpm --dir packages/core typecheck # validates *.spec-d.ts ``` + +When changing suspend/resume or checkpoint format, add round-trip tests to `suspend-resume.spec.ts`. For type changes, add `expectTypeOf` cases to `chain.spec-d.ts`. + +## Related + +- Parents: [`../../AGENTS.md`](../../AGENTS.md) (package), [`../../../../AGENTS.md`](../../../../AGENTS.md) (repo) +- Siblings: [`../agent/AGENTS.md`](../agent/AGENTS.md), [`../memory/AGENTS.md`](../memory/AGENTS.md), [`../workspace/AGENTS.md`](../workspace/AGENTS.md), [`../voltops/AGENTS.md`](../voltops/AGENTS.md), [`../tool/AGENTS.md`](../tool/AGENTS.md), [`../observability/AGENTS.md`](../observability/AGENTS.md), [`../mcp/AGENTS.md`](../mcp/AGENTS.md), [`../a2a/AGENTS.md`](../a2a/AGENTS.md) +- External: `../triggers/` (event-driven workflow starts) diff --git a/packages/core/src/workspace/AGENTS.md b/packages/core/src/workspace/AGENTS.md index 2c98cc282..cde96e455 100644 --- a/packages/core/src/workspace/AGENTS.md +++ b/packages/core/src/workspace/AGENTS.md @@ -1,21 +1,135 @@ # Core Workspace -This directory owns workspace filesystem access, sandbox execution, search, skills, timeouts, and tool policy. +**Security-sensitive.** Owns workspace filesystem access, sandbox execution, search (BM25 + optional vector), skills, timeouts, and tool policy. Local and sandbox implementations expose the same `Workspace` shape. -## Local Rules +## Critical Rules -- Treat filesystem and sandbox behavior as security-sensitive. Preserve path normalization, timeout handling, and tool-policy checks. -- Keep local filesystem behavior and sandbox behavior aligned where they expose the same workspace capability. -- Avoid broadening file access, command execution, or skill loading without explicit tests. -- Search behavior should remain deterministic and should not require network access. -- Preserve public workspace types and toolkit behavior used by agents. +- **Path normalization is non-negotiable.** All filesystem paths flow through `NodeFilesystemBackend.resolvePath()` (in `filesystem/backends/filesystem.ts`): backslashes → forward slashes, leading slashes stripped, `path.normalize()`, then containment check via `path.relative(this.cwd, abs)` (rejects `..` escapes and absolute path injection when `contained=true`, the default). +- **Tool policy is enforced before execution.** Filesystem, sandbox, search, and skills toolkits resolve per-tool `enabled` and `needsApproval` from `WorkspaceToolPolicy`. Removing or weakening these checks is a security regression. +- **Workspace operations honor `operationTimeoutMs` when configured.** Enforced by `withOperationTimeout()` in `timeout.ts` via `Promise.race` + `AbortSignal`. `Workspace` itself has no default — when undefined, enforcement is skipped. `LocalSandbox` separately defaults to 30000 ms. +- **Sandbox commands are tokenized, not shelled.** `normalizeCommandAndArgs()` in `sandbox/command-normalization.ts` parses quotes/escapes to extract command + args. No `shell: true` execution. +- **Search is deterministic and offline.** BM25 lexical (default `"bm25"`) or hybrid with vector embeddings. Auto-indexing runs at `Workspace.init()` via `ensureAutoIndex()`; failures log and are non-blocking. No network access in the search code path. +- **Skills are filesystem-bound.** Discovered from `rootPaths` (default `["/skills"]`) by glob (default `"**/SKILL.md"`). Agents access skills only through workspace tools. +- **`safeStringify` for telemetry attributes** — keep cardinality low; never log raw file content or absolute system paths. -## Tests +## Workspace Interface + +```ts +class Workspace { + readonly id: string; + readonly scope: "agent" | "conversation"; + readonly filesystem: WorkspaceFilesystem; + readonly sandbox?: WorkspaceSandbox; + readonly skills?: WorkspaceSkills; + status: "idle" | "initializing" | "ready" | "destroyed" | "error"; + + async init(): Promise; + async destroy(): Promise; + getInfo(): WorkspaceInfo; + getPathContext(): WorkspacePathContext; + getToolsConfig(): WorkspaceToolConfig | undefined; + + createFilesystemToolkit(options?): Toolkit; + createSandboxToolkit(options?): Toolkit; + createSearchToolkit(options?): Toolkit; + createSkillsToolkit(options?): Toolkit; + createSkillsPromptHook(options?): AgentHooks; +} +``` + +## Filesystem Safety + +`NodeFilesystemBackend.resolvePath(key: string): string` — single source of truth for path safety. With `contained=true` (default), the resolved absolute path is validated against `this.cwd`; any `..` or absolute injection rejects with an error before the backend touches disk. + +Backend interface methods (`backend.ts`): `read`, `write`, `edit`, `delete`, `stat`, `mkdir`, `grep`, `glob`. All implementations must preserve containment guarantees. + +## Sandbox + +```ts +interface WorkspaceSandbox { + name: string; + status?: "idle" | "ready" | "destroyed" | "error"; + start?(): Promise; + stop?(): Promise; + destroy?(): Promise; + getInfo?(): Record; + getInstructions?(): string | null; + execute(options: WorkspaceSandboxExecuteOptions): Promise; +} +``` + +`LocalSandbox` (`sandbox/local.ts`) wraps `child_process.spawn` with timeout, output buffering (default ~5 MB), and OS-specific isolation (`sandbox-exec` on macOS, `bwrap` on Linux when available). External providers live in `packages/sandbox-daytona` and `packages/sandbox-e2b`. + +## Search & Skills + +- **Auto-index**: configured via `WorkspaceSearchConfig.autoIndexPaths`, e.g., `[{ path: "/src", glob: "**/*.ts" }]`. Runs at `Workspace.init()`; partial failures retry with `OperationContext` and log on final failure. +- **Modes**: `"bm25"` (lexical) or `"hybrid"` (BM25 + vector). Default is `"hybrid"` when both an embedding adapter and a vector adapter are configured, otherwise `"bm25"`. Stays deterministic and offline. +- **Skills**: each skill is a directory containing `SKILL.md` plus optional `references/`, `scripts/`, `assets/`. Activated/deactivated via the workspace tools `workspace_activate_skill` / `workspace_deactivate_skill`. + +## Tool Policy + +```ts +type WorkspaceToolPolicy = { + enabled?: boolean; + needsApproval?: boolean | ((args: unknown) => boolean); +}; -Prefer targeted tests for filesystem, sandbox, search, skills, timeout, or policy changes: +type WorkspaceFilesystemToolPolicy = WorkspaceToolPolicy & { + requireReadBeforeWrite?: boolean; +}; +``` + +Policies pass into toolkit factories and are merged with workspace `toolConfig`. Resolved before each tool call: + +```ts +const policy = this.resolveSearchToolPolicy(name); +if (policy?.enabled === false) throw new Error(`Tool '${name}' is disabled by policy`); +if (policy?.needsApproval) throw new Error(`Tool '${name}' requires approval`); +``` + +## Boundaries (security model) + +**Allowed without asking** + +- Adding tests. +- Refactoring within existing safety guarantees. +- Improving error messages. +- Narrowing scopes. +- Adding telemetry that uses `safeStringify`. + +**Ask first** + +- Adding a new sandbox provider (validate isolation). +- Changing skill-loading semantics. +- Changing default `operationTimeoutMs`. +- Adding new toolkit types. +- Changing search algorithm or default mode. + +**Never without an explicit, security-reviewed request** + +- Broadening file access roots (`contained=false`). +- Removing or weakening path normalization. +- Removing tool policy enforcement. +- Removing timeout enforcement. +- Adding network calls to search/auto-index. +- Adding `shell: true` to sandbox execution. + +## Tests ```bash -pnpm --dir packages/core test:single -- src/workspace/path-to-test.spec.ts -pnpm --dir packages/core test +pnpm --dir packages/core test:single -- src/workspace/index.spec.ts +pnpm --dir packages/core test:single -- src/workspace/timeout.spec.ts +pnpm --dir packages/core test:single -- src/workspace/filesystem/index.spec.ts +pnpm --dir packages/core test:single -- src/workspace/filesystem/node-filesystem-backend.spec.ts +pnpm --dir packages/core test:single -- src/workspace/sandbox/local.spec.ts +pnpm --dir packages/core test:single -- src/workspace/search/index.spec.ts pnpm --dir packages/core typecheck ``` + +Coverage areas: path containment (`filesystem/`), command tokenization + isolation (`sandbox/`), BM25 ranking + auto-index retry (`search/`), skill discovery (`skills/`), policy merging + timeout enforcement (root + `timeout.spec.ts`). + +## Related + +- Parents: [`../../AGENTS.md`](../../AGENTS.md) (package), [`../../../../AGENTS.md`](../../../../AGENTS.md) (repo) +- Siblings: [`../agent/AGENTS.md`](../agent/AGENTS.md), [`../workflow/AGENTS.md`](../workflow/AGENTS.md), [`../memory/AGENTS.md`](../memory/AGENTS.md), [`../voltops/AGENTS.md`](../voltops/AGENTS.md), [`../tool/AGENTS.md`](../tool/AGENTS.md), [`../observability/AGENTS.md`](../observability/AGENTS.md), [`../mcp/AGENTS.md`](../mcp/AGENTS.md), [`../a2a/AGENTS.md`](../a2a/AGENTS.md) +- External sandbox providers: `packages/sandbox-daytona`, `packages/sandbox-e2b` diff --git a/packages/server-core/AGENTS.md b/packages/server-core/AGENTS.md new file mode 100644 index 000000000..85b3bdec8 --- /dev/null +++ b/packages/server-core/AGENTS.md @@ -0,0 +1,106 @@ +# @voltagent/server-core + +Framework-agnostic HTTP handlers, Zod schemas, OpenAPI generation, auth, and WebSocket utilities. Consumed by the runtime adapter packages (`server-hono`, `server-elysia`, `serverless-hono`); changes here ripple to every server runtime. + +## Critical Rules + +- **Detect Zod v3 vs v4 at runtime.** Switch via `"toJSONSchema" in z`. Use `zod-from-json-schema-v3` vs `zod-from-json-schema`. See `agent.handlers.ts` and `options.ts`. +- **Auth header conventions are wire contracts.** `x-voltagent-dev` (dev bypass, requires non-production `NODE_ENV`), `x-console-access-key` / `?key=` query (production console access via `VOLTAGENT_CONSOLE_ACCESS_KEY`). WebSocket upgrade applies the same logic. +- **`safeStringify` for all serialization.** Import from `@voltagent/internal`. Apply to WebSocket frames, SSE payloads, observability events, and logs. Never raw `JSON.stringify` in handlers. +- **Handler signatures are the adapter contract.** Renaming `handleGenerateText`, `handleStreamText`, `handleWorkflowExecute`, etc. or changing their (`deps, logger, body, params, signal?, requestHeaders?`) shape breaks every runtime adapter. Coordinate cross-package. +- **Routes live in `src/routes/definitions.ts`.** Add new endpoints there first (method, path, params, responses) before wiring an adapter. OpenAPI generation reads this file. +- **OpenAPI 3.1.0 generation is opt-in for Swagger UI in production.** `shouldEnableSwaggerUI(config)` defaults true in dev, false in production unless `enableSwaggerUI=true`. + +## Handlers + +All handlers are framework-agnostic with a common signature: `(deps: ServerProviderDeps, logger, body, params, signal?, requestHeaders?) → Promise`. `ApiResponse` is `{ success: boolean; data?: T; error?: string }`. + +**Streaming** (`handleStreamText`, `handleChatStream`, `handleWorkflowExecute`): SSE via `formatSSE` + `safeStringify`. Final frame is `[DONE]`. + +**Zod-version detection**: at runtime, branch on `"toJSONSchema" in z` — v4 path uses `zod-from-json-schema`, v3 path uses `zod-from-json-schema-v3`. + +## Schemas + +`createServerCoreSchemas(zod?)` is a factory: callers pass their own Zod instance to keep version alignment between adapter and core. Schemas use `.describe()` for OpenAPI generation. + +## Auth + +- **Dev bypass**: `isDevRequest(req)` → true iff `NODE_ENV !== "production"` AND `x-voltagent-dev: true` (or `?dev=true`). +- **Console access**: `hasConsoleAccess(req)` extends dev bypass; in production checks `x-console-access-key` (or `?key=`) against `VOLTAGENT_CONSOLE_ACCESS_KEY`. +- **JWT**: provider in `auth/providers/jwt`. +- Public/console route defaults in `DEFAULT_LEGACY_PUBLIC_ROUTES` / `DEFAULT_CONSOLE_ROUTES`. + +## WebSocket + +Server: `createWebSocketServer`. Upgrade: `setupWebSocketUpgrade` (same auth as HTTP). Observability: `setupObservabilityHandler` streams telemetry frames. Logs: `LogStreamManager` in `websocket/log-stream.ts`. + +## OpenAPI / Swagger + +`getOpenApiDoc(port, info?)` produces an OpenAPI 3.1.0 document from `routes/definitions.ts`. `shouldEnableSwaggerUI(config)` controls UI exposure (dev on, prod off unless explicit). + +## A2A & MCP Routes + +JSON-RPC 2.0 for A2A — error codes `PARSE_ERROR`, `METHOD_NOT_FOUND`, `TASK_NOT_FOUND`, etc. Agent card endpoint: `/agents/:id/card` returns `AgentCard`. MCP routes registered under `/mcp/*` via `src/mcp/`. + +## Code Examples + +```ts +// Good — Zod-version-flexible schema use +const z = zod; +const zodSchema = ("toJSONSchema" in z ? convertJsonSchemaToZod : convertJsonSchemaToZodV3)( + jsonSchema +) as any; +const result = await agent.generateObject(input, zodSchema, options); + +// Good — safeStringify for streaming + WS +import { safeStringify } from "@voltagent/internal"; +ws.send(safeStringify({ type: "text", content: "hello" })); + +// Good — auth gate before sensitive handler +if (requiresAuth(route, authConfig) && !hasConsoleAccess(req)) { + return { success: false, error: "Unauthorized" }; +} + +// Bad — raw JSON.stringify in production paths +ws.send(JSON.stringify(event)); // ✗ — use safeStringify +``` + +## Boundaries + +**Allowed without asking** + +- Adding focused handler tests. +- Internal refactors that preserve handler signatures. +- OpenAPI metadata edits (`description`, `summary`, `tags`). +- Additions to `DEFAULT_CONSOLE_ROUTES`. + +**Ask first** + +- Handler signature changes. +- New entries in `routes/definitions.ts` (wire-compat). +- Zod schema shape changes (cross-adapter break). +- Auth header / env var changes. +- OpenAPI version or generation strategy changes. + +**Never without explicit ask** + +- Removing handlers or public exports. +- Dropping Zod v3 or v4 support without coordinating across adapters. +- Changing JSON-RPC error codes or `AgentCard` shape. + +## Tests + +```bash +pnpm --dir packages/server-core test +pnpm --dir packages/server-core typecheck +# To run a single file, use vitest directly: +pnpm --dir packages/server-core exec vitest run src/handlers/agent.handlers.spec.ts +``` + +## Related + +- Parent: [`../../AGENTS.md`](../../AGENTS.md) (repo) +- Runtime adapters: `packages/server-hono`, `packages/server-elysia`, `packages/serverless-hono` +- Core runtime: [`../core/AGENTS.md`](../core/AGENTS.md) — agent/workflow/memory contracts +- External: `@voltagent/internal` (`safeStringify`, A2A types) +- Repo docs: [`../../contributing/changesets.md`](../../contributing/changesets.md) (versioning impact of handler changes) diff --git a/packages/server-core/CLAUDE.md b/packages/server-core/CLAUDE.md new file mode 120000 index 000000000..47dc3e3d8 --- /dev/null +++ b/packages/server-core/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file diff --git a/website/AGENTS.md b/website/AGENTS.md new file mode 100644 index 000000000..be6bfb403 --- /dev/null +++ b/website/AGENTS.md @@ -0,0 +1,65 @@ +# VoltAgent Website + +Docusaurus 3 docs + marketing site. Deployed independently from the published packages. + +## Critical Rules + +- **Uses npm, not pnpm.** This directory is **not** in `pnpm-workspace.yaml`. Install with `npm install` from `website/`. Don't `pnpm install` here — there's no `pnpm-lock.yaml` and you'll desync deps. +- **Plugin `index.js` files are generated.** `npm run build:plugins` runs sucrase on `plugins/**/*.ts` → `plugins/**/*.js` in place. Edit the `.ts`, re-run the script. Don't hand-edit the `.js`. +- **Markdown/MDX uses Prettier with the root config.** `lint-staged` runs `prettier --config ../.prettierrc --write` on `*.md` / `*.mdx`. JS/TS uses the local Biome (`biome check .`) — separate from the root Biome run. +- **Docusaurus version is pinned at `3.1.1`.** New plugins/themes must be `3.1.1`-compatible. A version bump is its own task. +- **`static/llms.txt` is hand-curated, not generated.** Verified: no build script, plugin, or hook produces it. Edit by hand and commit. ~38KB. Keep in sync with major doc additions or restructures — it's referenced from agent instructions across the repo. +- **Multiple docs trees, multiple sidebars.** Each top-level docs dir (`docs/`, `evaluation-docs/`, `models-docs/`, `observability/`, `actions-triggers-docs/`, `deployment-docs/`, `prompt-engineering-docs/`, `recipes/`) has its own `sidebars*.ts`. Adding a doc page means updating both the file and the matching sidebar. +- **Docusaurus SSRs.** Don't import Node-only modules (`fs`, `path`, `child_process`) at the top of client components — works in `npm run start` (Node-side), fails in `npm run build`. Wrap Node-only access in `useEffect` or ``. + +## Setup + +```bash +cd website +npm install +npm run start # dev server on :3000 +npm run start -- --port 3001 # different port if 3000 is busy +npm run start -- --host 0.0.0.0 # expose to LAN +``` + +## Validation + +```bash +npm run typecheck +npm run lint # Biome (website-local config) +npm run lint:fix +npm run build # full Docusaurus build — slow but the only real validation +``` + +## Boundaries + +**Allowed without asking** + +- Adding or editing docs, blog posts, recipes. +- Editing `sidebars*.ts`. +- Internal React components in `src/`. +- Tailwind class tweaks inside components. +- Adding static assets to `static/img/` (favicons, screenshots). + +**Ask first** + +- Docusaurus version bumps. +- New plugins or themes. +- Restructuring docs trees or renaming top-level dirs. +- Changing `static/llms.txt` structure (content additions are fine). +- Editing `docusaurus.config.ts`, `tailwind.config.js`, `tsconfig.json`, `babel.config.js`, local `biome.json`, or `lint-staged` config. +- Adding redirects via `@docusaurus/plugin-client-redirects`. +- Bumping any dependency in `package.json`. + +**Never without explicit ask** + +- Hand-editing transpiled `plugins/*/index.js`. +- Deleting docs without setting up a redirect (`@docusaurus/plugin-client-redirects` is wired up). +- Changing the deployment pipeline (GitHub Actions, deploy keys, hosting config). +- Committing `build/`, `.docusaurus/`, or `node_modules/`. + +## Related + +- Parent: [`../AGENTS.md`](../AGENTS.md) +- Sibling: `../examples/` (which **is** in the pnpm workspace, unlike this directory) +- Repo agent docs: [`../contributing/coding-agents.md`](../contributing/coding-agents.md) diff --git a/website/CLAUDE.md b/website/CLAUDE.md new file mode 120000 index 000000000..47dc3e3d8 --- /dev/null +++ b/website/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file From 4cfa7772304a44e57454c8eefbf7e12d902e8dc6 Mon Sep 17 00:00:00 2001 From: Zac Rosenbauer Date: Sat, 9 May 2026 21:05:06 -0400 Subject: [PATCH 4/4] build: skip AGENTS.md and CLAUDE.md in lint-staged MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Switches the root lint-staged config from a static block in package.json to lint-staged.config.mjs so we can filter files programmatically. Skips: - AGENTS.md — canonical coding-agent instruction docs; hand-curated, not worth mechanical Prettier churn. - CLAUDE.md — symlinks to AGENTS.md (managed by scripts/sync-agent-instructions.mjs). Prettier 3.x errors when handed symlink paths explicitly ("Explicitly specified pattern ... is a symbolic link"), which broke the pre-commit hook on AGENTS.md changes even when the underlying content was fine. Updates the husky pre-commit hook to drop `--config package.json` so lint-staged auto-discovers `lint-staged.config.mjs`. Also adds AGENTS.md and CLAUDE.md to .prettierignore as a defense-in-depth measure for direct `prettier --write` runs. Co-Authored-By: Claude --- .husky/pre-commit | 2 +- .prettierignore | 10 +++++++++- lint-staged.config.mjs | 36 ++++++++++++++++++++++++++++++++++++ package.json | 8 -------- 4 files changed, 46 insertions(+), 10 deletions(-) create mode 100644 lint-staged.config.mjs diff --git a/.husky/pre-commit b/.husky/pre-commit index 044922ae0..4ca2dd3e9 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -3,4 +3,4 @@ cd "$(git rev-parse --show-toplevel)" || exit 1 -npx lint-staged --config package.json +npx lint-staged diff --git a/.prettierignore b/.prettierignore index e26f0b3f7..c877ac472 100644 --- a/.prettierignore +++ b/.prettierignore @@ -2,4 +2,12 @@ /dist /coverage /.nx/cache -/.nx/workspace-data \ No newline at end of file +/.nx/workspace-data + +# Coding-agent instruction files. AGENTS.md is the canonical doc; CLAUDE.md +# (and any future aliases configured in scripts/sync-agent-instructions.mjs) +# are symlinks to it. Skipping both avoids two problems: +# 1. Prettier 3.x errors on symlink paths passed via lint-staged. +# 2. Mechanical reformatting churn on docs that are hand-curated for agents. +AGENTS.md +CLAUDE.md \ No newline at end of file diff --git a/lint-staged.config.mjs b/lint-staged.config.mjs new file mode 100644 index 000000000..62c02145d --- /dev/null +++ b/lint-staged.config.mjs @@ -0,0 +1,36 @@ +import { lstatSync } from "node:fs"; +import path from "node:path"; + +/** + * Files we never want lint-staged to format or lint. + * + * - `AGENTS.md` is the canonical coding-agent instruction file. It's + * hand-curated for agents and we don't want mechanical reformatting churn. + * - `CLAUDE.md` (and any other aliases registered in + * `scripts/sync-agent-instructions.mjs`) are symlinks to AGENTS.md. + * Prettier 3.x errors when handed a symlink path explicitly, which breaks + * the pre-commit hook even though the underlying content is fine. + */ +const ignoredFilenames = new Set(["AGENTS.md", "CLAUDE.md"]); + +const isIgnored = (file) => ignoredFilenames.has(path.basename(file)); + +/** Defensive guard for any other symlink that slips through `*.{md,mdx}`. */ +const isSymlink = (file) => { + try { + return lstatSync(file).isSymbolicLink(); + } catch { + return false; + } +}; + +const quote = (file) => JSON.stringify(file); + +export default { + "*.{js,jsx,ts,tsx}": ["biome check --write --no-errors-on-unmatched"], + "*.{md,mdx}": (files) => { + const filtered = files.filter((f) => !isIgnored(f) && !isSymlink(f)); + if (filtered.length === 0) return []; + return [`prettier --config ./.prettierrc --write ${filtered.map(quote).join(" ")}`]; + }, +}; diff --git a/package.json b/package.json index 94160b5dc..b1ed75fbe 100644 --- a/package.json +++ b/package.json @@ -43,14 +43,6 @@ "pnpm": ">=8" }, "license": "MIT", - "lint-staged": { - "*.{js,jsx,ts,tsx}": [ - "biome check --write --no-errors-on-unmatched" - ], - "*.{md,mdx}": [ - "prettier --config ./.prettierrc --write" - ] - }, "nx": {}, "packageManager": "pnpm@8.10.5", "private": true,