Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
0b1c0c9
feat: support multiple api providers
colinds Feb 19, 2026
d4cb114
chore: stricter types and bug fixes
colinds Feb 19, 2026
bfd693f
fix: bug fixes
colinds Feb 19, 2026
7a88e54
fix: support openai reasoning summaries
colinds Feb 19, 2026
0ac1823
chore: cleanup
colinds Feb 19, 2026
d93f40e
example: new codex subagent example
colinds Feb 19, 2026
c7d99dd
chore: improved types
colinds Feb 20, 2026
61fecff
chore: update deps and directly pass in the openai and anthropic inst…
colinds Feb 20, 2026
6231e0b
chore: peer deps, use bun catalog references, and improve client hand…
colinds Feb 20, 2026
41a1a8e
feat: add compaction example and improve error handling in execution …
colinds Feb 20, 2026
d68ae16
feat: enhance error logging and improve condition evaluation in execu…
colinds Feb 21, 2026
50bb2b0
feat: refactor condition evaluation for improved readability
colinds Feb 21, 2026
f5044a0
chore: add new linting rules
colinds Feb 21, 2026
a75ca85
feat: enhance agent component types
colinds Feb 21, 2026
8e93200
feat: simplify client handling
colinds Feb 21, 2026
5fba8de
cleanup
colinds Feb 21, 2026
5229fad
chore: readme fixes
colinds Feb 21, 2026
5fa625f
fix: correctly set the client on init
colinds Feb 21, 2026
3f85c7a
feat: use enums
colinds Feb 21, 2026
e801e37
chore: cleanup sdk tool
colinds Feb 21, 2026
4db935e
chore: cleanup old eslint references
colinds Feb 21, 2026
db83532
refactor: improve client handling and types
colinds Feb 21, 2026
fa59fac
chore: cleanup readme
colinds Feb 21, 2026
a6330ac
refactor: rename sdkTools to builtInTools
colinds Feb 22, 2026
0616686
refactor: streamline memory handlers and enhance debug logging
colinds Feb 22, 2026
885a7d6
refactor: cleanup
colinds Feb 22, 2026
9f3a99b
refactor: simplify exports and remove unused constants and types
colinds Feb 22, 2026
8d5c21b
fix: guard against empty assistant content array in anthropic adapter
colinds Feb 22, 2026
29f2694
chore: cleanup
colinds Feb 24, 2026
c4152db
feat: OpenAI WebSocket support and providers config refactor
colinds Feb 25, 2026
df65187
chore: improved error handling
colinds Feb 28, 2026
3ab52f4
refactor: move websocket to the Agent component
colinds Feb 28, 2026
c7b5e38
refactor: optimize updates
colinds Mar 1, 2026
6ced04a
refactor: update engine configuration and improve type handling
colinds Mar 1, 2026
ca4e681
feat: implement async-aware step finish callback and emit synthetic e…
colinds Mar 1, 2026
e9fe4b6
chore: update readme and examples
colinds Mar 1, 2026
74cca57
fix: reduce delay in async onStepFinish callback to fix tests not pas…
colinds Mar 1, 2026
ad3319c
refactor: enhance type handling and error management
colinds Mar 1, 2026
9f91c4a
refactor: enhance Condition and Agent components with provider/model …
colinds Mar 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .claude/hooks/format.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/usr/bin/env bash
set -e

input=$(cat)
file_path=$(echo "$input" | jq -r '.tool_input.file_path')

if [[ -z "$file_path" || ! -f "$file_path" ]]; then
exit 0
fi

if [[ "$file_path" =~ \.(js|jsx|ts|tsx)$ ]]; then
bunx oxlint --fix "$file_path" 2>/dev/null || true
bunx oxfmt --write "$file_path" 2>/dev/null || true
fi

exit 0
16 changes: 16 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "bash .claude/hooks/format.sh",
"timeout": 15
}
]
}
]
}
}
6 changes: 6 additions & 0 deletions .cursor/hooks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"version": 1,
"hooks": {
"afterFileEdit": [{ "command": "./.cursor/hooks/format.sh" }]
}
}
16 changes: 16 additions & 0 deletions .cursor/hooks/format.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/usr/bin/env bash
set -e

input=$(cat)
file_path=$(echo "$input" | jq -r '.file_path')

if [[ -z "$file_path" || ! -f "$file_path" ]]; then
exit 0
fi

if [[ "$file_path" =~ \.(js|jsx|ts|tsx)$ ]]; then
bunx oxlint --fix "$file_path" 2>/dev/null || true
bunx oxfmt --write "$file_path" 2>/dev/null || true
fi

exit 0
7 changes: 6 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,13 @@ jobs:
run: bun run test
- name: Update npm
run: npm install -g npm@latest
- name: Pack
run: bun pm pack
working-directory: packages/agentry
- name: Publish to npm
run: npm publish --provenance --access public
run: |
VERSION=$(jq -r '.version' package.json)
npm publish "agentry-${VERSION}.tgz" --provenance --access public
working-directory: packages/agentry
- name: Create GitHub release
uses: softprops/action-gh-release@v2
Expand Down
6 changes: 5 additions & 1 deletion .oxlintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": null,
"categories": {},
"rules": {},
"rules": {
"no-unused-vars": "error",
"unicorn/no-nested-ternary": "error",
"max-params": "error"
},
"settings": {
"jsx-a11y": {
"polymorphicPropName": null,
Expand Down
254 changes: 52 additions & 202 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,235 +2,85 @@

## Overview

Agentry is a React reconciler-based framework for declarative AI agent orchestration. It uses a custom React reconciler to translate JSX into agent execution plans, bringing React's declarative component model to AI agent systems.
Agentry is a React reconciler-based framework for declarative AI agent orchestration.
It supports multiple providers (Anthropic + OpenAI) through a shared core API with a single `run` plus `createAI` defaults.

## Development Commands

### Testing

- `bun test` - Run all tests across packages
- `bun test --timeout 100 ./packages/**/tests` - Full test suite (as configured)
- `bun test packages/core/tests/tools.test.ts` - Run a specific test file
- `bun test` - Run the full Agentry test suite (`packages/agentry/tests`)
- `bun test packages/agentry/tests/openai-provider.test.tsx` - OpenAI provider tests
- `bun test packages/agentry/tests/runtime.test.tsx` - Anthropic/core runtime coverage

### Type Checking & Linting

- `bun run typecheck` - Type check all packages (runs `tsc --noEmit`)
- `bun run lint` - Lint codebase with ESLint (max 0 warnings)
- `bun run lint:fix` - Auto-fix linting issues
- `bun run format` - Check formatting with Prettier
- `bun run format:fix` - Auto-format with Prettier
- `bun run typecheck` - Type check all workspace packages
- `bun run lint` - Lint source and examples
- `bun run lint:fix` - Auto-fix lint issues
- `bun run format` - Check formatting
- `bun run format:fix` - Auto-format

### Running Examples

Examples are in `packages/examples/src/`. Run with:
Examples live in `packages/examples/src/`.

```bash
bun run example:<name>
# e.g., bun run example:basic, bun run example:subagents
# provider-agnostic: example:basic
# provider-specific: example:anthropic:chatbot, example:openai:basic
```

Or directly:
## Repository Layout

```bash
bun run packages/examples/src/<example-name>.tsx
```

## Architecture

### Package Structure (Monorepo with Bun Workspaces)

- **`packages/core`** - Core reconciler and execution engine
- `src/reconciler/` - React reconciler implementation that translates JSX to agent instances
- `src/execution/` - ExecutionEngine that manages agent/API interaction lifecycle
- `src/handles/` - AgentHandle and SubagentHandle for controlling agent execution
- `src/instances/` - Instance types representing reconciled elements (AgentInstance, ToolInstance, etc.)
- `src/tools/` - Tool definition and execution utilities
- `src/run/` - Running functions: `agent.ts`, `subagent.ts`, `runAgentFunction.ts`
- `src/types/` - Shared TypeScript types
- `tests/` - Core functionality tests

- **`packages/components`** - React components for agent orchestration
- JSX components: `<Agent>`, `<Tool>`, `<AgentTool>`, `<System>`, `<Context>`, `<Message>`, `<Tools>`, `<Condition>`
- Built-in tools: `<WebSearch>`, `<CodeExecution>`, `<Memory>`, `<MCP>`
- Hooks: `useExecutionState()`, `useMessages()`, `useAgentState()`

- **`packages/agentry`** - Main entry point package
- Re-exports from `@agentry/core` and `@agentry/components`
- Single import point for users: `import { run, Agent, ... } from 'agentry'`

- **`packages/shared`** - Shared constants
- `MODEL`, `TEST_MODEL` constants

- **`packages/examples`** - Example applications demonstrating framework features

### Key Architectural Concepts

**React Reconciler Integration**

- The reconciler (`packages/core/src/reconciler/reconciler.ts`) translates JSX into instance objects
- Instance types (`packages/core/src/instances/types.ts`) represent the reconciled tree structure
- The `createInstance` function builds instances from element types and props
- Instances contain both component configuration and runtime state

**Execution Flow**

1. `run()` creates an AgentHandle with the JSX element
2. AgentHandle uses the reconciler to build an AgentInstance tree
3. ExecutionEngine manages the conversation loop with the Anthropic API
4. Tool calls trigger handler execution and potential re-renders (for dynamic tools)
5. State updates via hooks can modify the instance tree mid-execution

**Subagents (AgentTool)**

- Subagents are created using `<AgentTool>` component, NOT by directly nesting `<Agent>` in `<Tools>`
- AgentTool takes an `agent` prop - a function that receives parsed parameters and returns an `<Agent>` JSX element
- When the parent calls the tool, the framework creates a SubagentHandle to execute the agent
- SubagentHandle renders the JSX returned by the `agent` function and manages its lifecycle
- Subagent results flow back as tool results to parent agent

Example pattern:

```tsx
<AgentTool
name="researcher"
description="Research specialist"
parameters={z.object({ topic: z.string() })}
agent={(input) => (
<Agent name="researcher">
<System>You are a research expert.</System>
<Message role="user">Research the topic: {input.topic}</Message>
</Agent>
)}
/>
```

**Programmatic Agent Spawning**

- Tool handlers receive a `ToolContext` with a `runAgent()` function
- `runAgent()` allows programmatically creating and executing agents from within tool handlers
- Spawned agents run to completion and return their full `AgentResult` to the handler
- Results are only visible to the tool handler (not to Claude in the parent conversation)
- Supports conditional spawning, parallel execution, and custom configuration per spawned agent
- Implemented via `createRunAgent()` which creates a bound function from execution context
- The spawned agent is executed as a subagent using `SubagentHandle`

Example pattern:

```tsx
<Tool
name="analyze"
handler={async (input, context) => {
const result = await context.runAgent(
<Agent name="analyst">
<System>You are an analyst</System>
<Message role="user">Analyze: {input.data}</Message>
</Agent>,
{ maxTokens: 2048 }, // Optional config override
)
return result.content
}}
/>
```

**Conditional Rendering (Condition)**

- Condition components enable conditional rendering based on state or natural language intent
- Conditions can wrap any agent components (System, Context, Tools, etc.)
- Boolean conditions (`when={boolean}`) evaluated synchronously before each API call
- Natural language conditions (`when="..."`) evaluated via LLM based on conversation context
- Condition evaluation happens in `ExecutionEngine.evaluateAllConditions()` before building the API request
- Multiple conditions can be active simultaneously (parallel evaluation)
- Condition evaluation implemented in `evaluateConditions()` which:
- First checks all boolean conditions synchronously
- Then evaluates NL conditions via single batched LLM call
- Updates `isActive` state on each ConditionInstance
- ConditionInstance tracks `isActive` which is checked by collectors
- Collectors (`collectChild`) only collect children from conditions where `isActive === true`
- Conditions can be nested - both parent and child must be active for grandchildren to be collected

Example pattern:

```tsx
<Condition when={isAuthenticated}>
<System>You are authenticated</System>
<Tools><Tool name="protected_action" ... /></Tools>
</Condition>

<Condition when="user wants to do math">
<Tools><Tool name="calculate" ... /></Tools>
</Condition>

{/* Nested conditions */}
<Condition when={isAuthenticated}>
<Condition when={isPremium}>
<Tools><Tool name="premium_feature" ... /></Tools>
</Condition>
</Condition>
```

**Dynamic Tools**

- Tools can be added/removed during execution using React state (useState)
- State changes trigger reconciler updates, modifying the instance tree
- Next API call includes the updated tool set

**Batch vs Interactive Mode**

- **Batch mode** (default): Runs to completion, returns AgentResult
- **Interactive mode**: Returns AgentHandle for multi-turn conversations

## Testing

Tests use Bun's built-in test runner. Mock client available via `createStepMockClient` from `tests/utils`.

Common test patterns:

- Import test utilities: `import { createStepMockClient } from './utils'`
- Use `expect` from `bun:test`
- Mock Anthropic responses for deterministic testing
- `packages/agentry` - Framework package
- `src/reconciler` - custom React reconciler
- `src/execution` - execution loop and condition evaluation
- `src/handles` - agent/subagent handles
- `src/providers` - provider adapters (`anthropic`, `openai`)
- `src/components` - JSX API (`Agent`, `Tool`, `AgentTool`, etc.)
- `src/tools` - tool definition/parsing/execution helpers
- `src/run` - `run`, `createAgent`, `runAgent` wiring
- `tests` - unit/integration tests (including OpenAI provider tests)
- `packages/examples` - runnable examples and DX references

## TypeScript Configuration
## Current Architecture Notes

- Target: ESNext with bundler module resolution
- Strict mode enabled with additional checks (`noUncheckedIndexedAccess`, `noImplicitOverride`)
- JSX runtime: `react-jsx` with React 19
- Module: `Preserve` (uses TypeScript's new module preservation)
- No emit mode (bundler handles compilation)
### Providers and Exports

## Important Implementation Notes
- Root `agentry` exports provider-agnostic primitives (`run`, `createAI`, `Agent`, `Tool`, hooks).
- Provider modules:
- `agentry/anthropic` exports Anthropic client factory + Anthropic built-ins (`WebSearch`, `CodeExecution`, `Memory`, `MCP`)
- `agentry/openai` exports OpenAI client factory + OpenAI-compatible built-ins (`WebSearch`, `CodeExecution`, `MCP`)
- Built-ins are treated as regular tools at runtime; there is no hardcoded per-provider capability matrix in the engine.
- Provider selection is resolved from `<Agent provider=\"...\">` (and subagents), while `run`/`createAI` provide clients and runtime defaults.

**Tool Definition**
### SDK Type Reuse

- Use `defineTool()` for programmatic regular tool creation with Zod schemas
- Use `<Tool>` JSX for inline declarative tool registration
- Use `defineAgentTool()` for programmatic subagent tool creation
- Use `<AgentTool>` JSX for inline declarative subagent registration
- Handler/agent function params are automatically inferred from Zod schema types
- Tool handlers receive `ToolContext` which includes `runAgent()` for programmatic agent running
- `runAgent()` is created via `createRunAgent()` and bound to the execution context (client, model, signal)
- Prefer SDK-owned tool/request/response types where possible.
- Avoid redefining provider-specific wire types when SDK types are available.
- Keep framework-level normalized types focused on orchestration concerns only.

**Instance Tree Mutations**
### Subagents

- The reconciler maintains an instance tree that can be mutated during execution
- Always use the reconciler's update/append/remove methods (via collections handlers)
- Manual instance modifications should go through proper reconciler channels
- `<AgentTool>` supports declarative subagents.
- `context.runAgent(...)` supports programmatic subagent spawning.
- Cross-provider subagents are supported (for example, OpenAI parent + Anthropic child, and vice versa).

**Agent State Management**
### Conditions

- AgentStore (Zustand) manages global agent state
- ExecutionEngine emits state change events
- Hooks subscribe to store updates for reactive behavior
- Boolean conditions evaluate synchronously before each API call.
- Natural-language conditions are evaluated in a batched model call.
- Conditions can be nested; active parent + active child is required for nested content.

**Error Handling**
## Workflow

- Tools should return error strings rather than throw (allows agent to see and handle errors)
- ExecutionEngine catches and transitions to 'error' state on critical failures
- SubagentHandle automatically cleans up on completion or error
- Run agents (via `runAgent()`) handle errors the same way - errors can be caught in the tool handler
- After finishing a set of changes, always run `bun test`, `bun run typecheck`, `bun run lint`, and `bun run format` before considering the work done.

**Run Functions**
## Implementation Guidelines

- `run/agent.ts` - Main `run()` function and `createAgent()` utility
- `run/subagent.ts` - Internal `runSubagent()` function for executing subagents
- `run/runAgent.ts` - `createRunAgent()` function for programmatic agent running
- All exports are re-exported from `run/index.ts` for backward compatibility
- Keep strict typing (`no any` / `no unknown` type annotations in implementation code paths).
- Prefer provider SDK types over duplicated local provider shapes.
- Return tool errors as strings so the model can recover.
- Preserve dynamic tool behavior (state-driven add/remove during execution).
- Do not reintroduce provider capability flags for built-ins unless explicitly requested.
Loading