diff --git a/content/Agents/creating-agents.mdx b/content/Agents/creating-agents.mdx
index c6ad7334..0c0e570e 100644
--- a/content/Agents/creating-agents.mdx
+++ b/content/Agents/creating-agents.mdx
@@ -197,7 +197,15 @@ All implement [StandardSchema](https://github.com/standard-schema/standard-schem
### Type Inference
-TypeScript automatically infers types from your schemas:
+TypeScript automatically infers types from your schemas. Don't add explicit type annotations to handler parameters:
+
+```typescript
+// Good: types inferred from schema
+handler: async (ctx, input) => { ... }
+
+// Bad: explicit types can cause issues
+handler: async (ctx: AgentContext, input: MyInput) => { ... }
+```
```typescript
const agent = createAgent('Search', {
@@ -364,6 +372,10 @@ handler: async (ctx, input) => {
## Next Steps
+
+The [OpenCode plugin](/Reference/CLI/opencode-plugin) provides AI-assisted development for full-stack Agentuity projects, including agents, routes, frontend, and deployment.
+
+
- [Using the AI SDK](/Agents/ai-sdk-integration): Add LLM capabilities with generateText and streamText
- [Managing State](/Agents/state-management): Persist data across requests with thread and session state
- [Calling Other Agents](/Agents/calling-other-agents): Build multi-agent workflows
diff --git a/content/Agents/evaluations.mdx b/content/Agents/evaluations.mdx
index fd0c1804..c9f56323 100644
--- a/content/Agents/evaluations.mdx
+++ b/content/Agents/evaluations.mdx
@@ -5,6 +5,21 @@ description: Automatically test and validate agent outputs for quality and compl
Evaluations (evals) are automated tests that run after your agent completes. They validate output quality, check compliance, and monitor performance without blocking agent responses.
+## Why Evals?
+
+Most evaluation tools test the LLM: did the model respond appropriately? That's fine for chatbots, but agents aren't single LLM calls. They're entire runs with multiple model calls, tool executions, and orchestration working together.
+
+Agent failures can happen anywhere in the run—a tool call that returned bad data, a state bug that corrupted context, and more. Testing just the LLM response misses most of this.
+
+Agentuity evals test the whole run—every tool call, state change, and orchestration step. They run on every session in production, so you catch issues with real traffic.
+
+**The result:**
+
+- **Full-run evaluation**: Test the entire agent execution, not just LLM responses
+- **Production monitoring**: Once configured, evals run automatically on every session
+- **Async by default**: Evals don't block responses, so users aren't waiting
+- **Preset library**: Common checks (PII, safety, hallucination) available out of the box
+
Evals come in two types: **binary** (pass/fail) for yes/no criteria, and **score** (0-1) for quality gradients.
@@ -426,6 +441,37 @@ export const politenessCheck = agent.createEval(politeness({
All preset evals use a default model optimized for cost and speed. Override `model` when you need specific capabilities.
+### Lifecycle Hooks
+
+Preset evals support `onStart` and `onComplete` hooks for custom logic around eval execution:
+
+```typescript
+import { politeness } from '@agentuity/evals';
+
+export const politenessCheck = agent.createEval(politeness({
+ onStart: async (ctx, input, output) => {
+ ctx.logger.info('Starting politeness eval', {
+ inputLength: input.request?.length,
+ });
+ },
+ onComplete: async (ctx, result) => {
+ // Track results in external monitoring
+ if (!result.passed) {
+ ctx.logger.warn('Politeness check failed', {
+ score: result.score,
+ reason: result.reason,
+ });
+ }
+ },
+}));
+```
+
+**Use cases for lifecycle hooks:**
+- Log eval execution for debugging
+- Send results to external monitoring systems
+- Track eval performance metrics
+- Trigger alerts on failures
+
### Schema Middleware
Preset evals expect a standard input/output format:
diff --git a/content/Agents/schema-libraries.mdx b/content/Agents/schema-libraries.mdx
index d8e4a207..b34d45c2 100644
--- a/content/Agents/schema-libraries.mdx
+++ b/content/Agents/schema-libraries.mdx
@@ -103,6 +103,29 @@ type User = s.infer;
// { name: string; age: number; role: 'admin' | 'user' }
```
+### JSON Schema Generation
+
+Convert schemas to JSON Schema for use with LLM structured output:
+
+```typescript
+import { s } from '@agentuity/schema';
+
+const ResponseSchema = s.object({
+ answer: s.string(),
+ confidence: s.number(),
+});
+
+// Generate JSON Schema
+const jsonSchema = s.toJSONSchema(ResponseSchema);
+
+// Generate strict JSON Schema for LLM structured output
+const strictSchema = s.toJSONSchema(ResponseSchema, { strict: true });
+```
+
+
+Use `{ strict: true }` when generating schemas for LLM structured output (e.g., OpenAI's `response_format`). Strict mode ensures the schema is compatible with model constraints and produces more reliable outputs.
+
+
Use `@agentuity/schema` for simple validation needs. For advanced features like email validation, string length constraints, or complex transformations, consider Zod or Valibot.
diff --git a/content/Agents/standalone-execution.mdx b/content/Agents/standalone-execution.mdx
index c402cb4b..5076fca6 100644
--- a/content/Agents/standalone-execution.mdx
+++ b/content/Agents/standalone-execution.mdx
@@ -16,10 +16,20 @@ import { createAgentContext } from '@agentuity/runtime';
import chatAgent from '@agent/chat';
const ctx = createAgentContext();
-const result = await ctx.invoke(() => chatAgent.run({ message: 'Hello' }));
+const result = await ctx.run(chatAgent, { message: 'Hello' });
```
-The `invoke()` method executes your agent with full infrastructure support: tracing, session management, and access to all storage services.
+The `run()` method executes your agent with full infrastructure support: tracing, session management, and access to all storage services.
+
+For agents that don't require input:
+
+```typescript
+const result = await ctx.run(statusAgent);
+```
+
+
+The older `ctx.invoke(() => agent.run(input))` pattern still works but `ctx.run(agent, input)` is preferred for its cleaner syntax.
+
## Options
@@ -45,10 +55,7 @@ await createApp();
// Run cleanup every hour
cron.schedule('0 * * * *', async () => {
const ctx = createAgentContext({ trigger: 'cron' });
-
- await ctx.invoke(async () => {
- await cleanupAgent.run({ task: 'expired-sessions' });
- });
+ await ctx.run(cleanupAgent, { task: 'expired-sessions' });
});
```
@@ -58,25 +65,23 @@ For most scheduled tasks, use the [`cron()` middleware](/Routes/cron) instead. I
## Multiple Agents in Sequence
-Run multiple agents within a single `invoke()` call to share the same session and tracing context:
+Run multiple agents in sequence with the same context:
```typescript
const ctx = createAgentContext();
-const result = await ctx.invoke(async () => {
- // First agent analyzes the input
- const analysis = await analyzeAgent.run({ text: userInput });
-
- // Second agent generates response based on analysis
- const response = await respondAgent.run({
- analysis: analysis.summary,
- sentiment: analysis.sentiment,
- });
+// First agent analyzes the input
+const analysis = await ctx.run(analyzeAgent, { text: userInput });
- return response;
+// Second agent generates response based on analysis
+const response = await ctx.run(respondAgent, {
+ analysis: analysis.summary,
+ sentiment: analysis.sentiment,
});
```
+Each `ctx.run()` call shares the same session and tracing context.
+
## Reusing Contexts
Create a context once and reuse it for multiple invocations:
@@ -84,9 +89,9 @@ Create a context once and reuse it for multiple invocations:
```typescript
const ctx = createAgentContext({ trigger: 'websocket' });
-// Each invoke() gets its own session and tracing span
+// Each run() gets its own session and tracing span
websocket.on('message', async (data) => {
- const result = await ctx.invoke(() => messageAgent.run(data));
+ const result = await ctx.run(messageAgent, data);
websocket.send(result);
});
```
@@ -104,6 +109,28 @@ Standalone contexts provide the same infrastructure as HTTP request handlers:
- **Session events**: Start/complete events for observability
+## Detecting Runtime Context
+
+Use `isInsideAgentRuntime()` to check if code is running within the Agentuity runtime:
+
+```typescript
+import { isInsideAgentRuntime, createAgentContext } from '@agentuity/runtime';
+import myAgent from '@agent/my-agent';
+
+async function processRequest(data: unknown) {
+ if (isInsideAgentRuntime()) {
+ // Already in runtime context, call agent directly
+ return myAgent.run(data);
+ }
+
+ // Outside runtime, create context first
+ const ctx = createAgentContext();
+ return ctx.run(myAgent, data);
+}
+```
+
+This is useful for writing utility functions that work both inside agent handlers and in standalone scripts.
+
## Next Steps
- [Calling Other Agents](/Agents/calling-other-agents): Agent-to-agent communication patterns
diff --git a/content/Agents/streaming-responses.mdx b/content/Agents/streaming-responses.mdx
index 9edc0b70..6c041339 100644
--- a/content/Agents/streaming-responses.mdx
+++ b/content/Agents/streaming-responses.mdx
@@ -50,6 +50,10 @@ Streaming requires both: `schema.stream: true` in your agent (so the handler ret
Enable streaming by setting `stream: true` in your schema and returning a `textStream`:
+
+The `textStream` from AI SDK's `streamText()` works directly with Agentuity's streaming middleware. Return it from your handler without additional processing.
+
+
```typescript
import { createAgent } from '@agentuity/runtime';
import { streamText } from 'ai';
diff --git a/content/Agents/workbench.mdx b/content/Agents/workbench.mdx
index c45ce29c..f0e1e01c 100644
--- a/content/Agents/workbench.mdx
+++ b/content/Agents/workbench.mdx
@@ -5,6 +5,19 @@ description: Use the built-in development UI to test agents, validate schemas, a
Workbench is a built-in UI for testing your agents during development. It automatically discovers your agents, displays their input/output schemas, and lets you execute them with real inputs.
+## Why Workbench?
+
+Testing agents isn't like testing traditional APIs. You need to validate input schemas, see how responses format, test multi-turn conversations, and understand execution timing. Using `curl` or Postman means manually constructing JSON payloads and parsing responses.
+
+Workbench understands your agents. It reads your schemas, generates test forms, maintains conversation threads, and shows execution metrics. When something goes wrong, you see exactly what the agent received and returned.
+
+**Key capabilities:**
+
+- **Schema-aware testing**: Input forms generated from your actual schemas
+- **Thread persistence**: Test multi-turn conversations without manual state tracking
+- **Execution metrics**: See token usage and response times for every request
+- **Quick iteration**: Test prompts display in the UI for one-click execution
+
## Enabling Workbench
Add a `workbench` section to your `agentuity.config.ts`:
diff --git a/content/Get-Started/quickstart.mdx b/content/Get-Started/quickstart.mdx
index f9854aa4..576d2218 100644
--- a/content/Get-Started/quickstart.mdx
+++ b/content/Get-Started/quickstart.mdx
@@ -212,6 +212,14 @@ After your first deployment, the App populates with:
## What's Next?
+
+Install the [OpenCode plugin](/Reference/CLI/opencode-plugin) for AI-assisted agent development. Get help writing agents, debugging, and deploying directly from your editor:
+
+```bash
+agentuity ai opencode install
+```
+
+
**Learn the concepts:**
- [Understanding How Agents Work](/Learn/Cookbook/Tutorials/understanding-agents): Tools, loops, and autonomous behavior
diff --git a/content/Learn/Cookbook/Patterns/llm-as-a-judge.mdx b/content/Learn/Cookbook/Patterns/llm-as-a-judge.mdx
new file mode 100644
index 00000000..69a8b2dd
--- /dev/null
+++ b/content/Learn/Cookbook/Patterns/llm-as-a-judge.mdx
@@ -0,0 +1,368 @@
+---
+title: LLM as a Judge
+description: Use LLMs to evaluate and score agent outputs for quality, safety, and compliance
+---
+
+The LLM-as-judge pattern uses one model to evaluate another model's output. This is useful for subjective quality assessments that can't be checked programmatically.
+
+## When to Use This Pattern
+
+| Use Case | Good Fit? |
+|----------|-----------|
+| Checking factual accuracy against sources | Yes |
+| Evaluating tone and politeness | Yes |
+| Detecting hallucinations in RAG | Yes |
+| Comparing response quality | Yes |
+| Validating JSON structure | No (use schema validation) |
+| Checking string length | No (use code) |
+
+Use LLM-as-judge when the evaluation requires understanding context, nuance, or subjective criteria.
+
+## Inline vs Evals
+
+LLM-as-judge can be used in two contexts:
+
+| Context | When to Use | Blocks Response? |
+|---------|-------------|------------------|
+| **Inline** (in handler) | Scores needed in UI, model comparison, user-facing quality metrics | Yes |
+| **Evals** (in eval.ts) | Background quality monitoring, compliance checks, production observability | No |
+
+**Inline example**: A model arena that shows users which response "won" needs scores returned with the response.
+
+**Eval example**: Checking all responses for PII or hallucinations in production without slowing down users.
+
+
+Agentuity has a built-in [evaluation system](/Agents/evaluations) that runs LLM-as-judge checks asynchronously after each response. Use evals for production monitoring without impacting response times.
+
+
+## Basic Pattern
+
+Use a fast model to judge outputs. Groq provides low-latency inference for judge calls:
+
+```typescript
+import { createAgent } from '@agentuity/runtime';
+import { generateText, generateObject } from 'ai';
+import { openai } from '@ai-sdk/openai';
+import { groq } from '@ai-sdk/groq';
+import { z } from 'zod';
+
+const JudgmentSchema = z.object({
+ score: z.number().min(0).max(1).describe('Quality score from 0 to 1'),
+ passed: z.boolean().describe('Whether the response meets quality threshold'),
+ reason: z.string().describe('Brief explanation of the judgment'),
+});
+
+const agent = createAgent('QualityChecker', {
+ schema: {
+ input: z.object({ question: z.string() }),
+ output: z.object({
+ answer: z.string(),
+ judgment: JudgmentSchema,
+ }),
+ },
+ handler: async (ctx, input) => {
+ // Generate answer with primary model
+ const { text: answer } = await generateText({
+ model: openai('gpt-5-mini'),
+ prompt: input.question,
+ });
+
+ // Judge with fast model
+ const { object: judgment } = await generateObject({
+ model: groq('openai/gpt-oss-120b'),
+ schema: JudgmentSchema,
+ prompt: `Evaluate this response on a 0-1 scale.
+
+Question: ${input.question}
+Response: ${answer}
+
+Consider:
+- Does it directly answer the question?
+- Is the information accurate?
+- Is it clear and easy to understand?
+
+Score 0.7+ to pass.`,
+ });
+
+ return { answer, judgment };
+ },
+});
+
+export default agent;
+```
+
+## Model Comparison Arena
+
+Compare responses from multiple providers and declare a winner. This pattern is useful for benchmarking, A/B testing, or letting users see quality differences:
+
+```typescript
+import { createAgent } from '@agentuity/runtime';
+import { generateText, generateObject } from 'ai';
+import { openai } from '@ai-sdk/openai';
+import { anthropic } from '@ai-sdk/anthropic';
+import { groq } from '@ai-sdk/groq';
+import { z } from 'zod';
+
+const ArenaJudgment = z.object({
+ winner: z.enum(['openai', 'anthropic']),
+ reasoning: z.string(),
+ scores: z.object({
+ openai: z.number().min(0).max(1),
+ anthropic: z.number().min(0).max(1),
+ }),
+});
+
+const arenaAgent = createAgent('ModelArena', {
+ schema: {
+ input: z.object({
+ prompt: z.string(),
+ tone: z.enum(['whimsical', 'suspenseful', 'comedic']),
+ }),
+ output: z.object({
+ results: z.array(z.object({
+ provider: z.string(),
+ story: z.string(),
+ generationMs: z.number(),
+ })),
+ judgment: ArenaJudgment,
+ }),
+ },
+ handler: async (ctx, input) => {
+ // Generate from both models in parallel
+ const [openaiResult, anthropicResult] = await Promise.all([
+ generateWithTiming(openai('gpt-5-nano'), input.prompt, input.tone),
+ generateWithTiming(anthropic('claude-haiku-4-5'), input.prompt, input.tone),
+ ]);
+
+ const results = [
+ { provider: 'openai', ...openaiResult },
+ { provider: 'anthropic', ...anthropicResult },
+ ];
+
+ // Judge with fast model
+ const { object: judgment } = await generateObject({
+ model: groq('openai/gpt-oss-120b'),
+ schema: ArenaJudgment,
+ prompt: `Compare these ${input.tone} stories and pick a winner.
+
+PROMPT: "${input.prompt}"
+
+--- OpenAI ---
+${openaiResult.story}
+
+--- Anthropic ---
+${anthropicResult.story}
+
+Score each on creativity, engagement, and tone match (0-1).
+Declare a winner with reasoning.`,
+ });
+
+ ctx.logger.info('Arena complete', { winner: judgment.winner });
+ return { results, judgment };
+ },
+});
+
+async function generateWithTiming(model: any, prompt: string, tone: string) {
+ const start = Date.now();
+ const { text } = await generateText({
+ model,
+ system: `Write a short ${tone} story (max 200 words) with a beginning, middle, and end.`,
+ prompt,
+ });
+ return { story: text, generationMs: Date.now() - start };
+}
+
+export default arenaAgent;
+```
+
+
+The [SDK Explorer](/) includes a working Model Arena demo that demonstrates this pattern in more detail.
+
+
+## Grounding Check for RAG
+
+Detect hallucinations by checking if claims are supported by retrieved sources:
+
+```typescript
+import { createAgent } from '@agentuity/runtime';
+import { generateText, generateObject } from 'ai';
+import { openai } from '@ai-sdk/openai';
+import { groq } from '@ai-sdk/groq';
+import { z } from 'zod';
+
+const GroundingJudgment = z.object({
+ isGrounded: z.boolean().describe('Whether all claims are supported by sources'),
+ score: z.number().min(0).max(1).describe('Percentage of claims supported'),
+ unsupportedClaims: z.array(z.string()).describe('Claims not found in sources'),
+ reason: z.string(),
+});
+
+const ragAgent = createAgent('RAGWithGrounding', {
+ schema: {
+ input: z.object({ question: z.string() }),
+ output: z.object({
+ answer: z.string(),
+ grounding: GroundingJudgment,
+ sources: z.array(z.string()),
+ }),
+ },
+ handler: async (ctx, input) => {
+ // Retrieve relevant documents
+ const results = await ctx.vector.search('knowledge-base', {
+ query: input.question,
+ limit: 3,
+ });
+
+ const sources = results.map(r => r.metadata?.content || '').filter(Boolean);
+
+ // Generate answer
+ const { text: answer } = await generateText({
+ model: openai('gpt-5-mini'),
+ prompt: `Answer based on these sources:\n\n${sources.join('\n\n')}\n\nQuestion: ${input.question}`,
+ });
+
+ // Check grounding with fast model
+ const { object: grounding } = await generateObject({
+ model: groq('openai/gpt-oss-120b'),
+ schema: GroundingJudgment,
+ prompt: `Check if this answer is supported by the sources.
+
+Answer: ${answer}
+
+Sources:
+${sources.map((s, i) => `[${i + 1}] ${s}`).join('\n\n')}
+
+Are all factual claims in the answer supported by the sources?
+List any unsupported claims.`,
+ });
+
+ if (!grounding.isGrounded) {
+ ctx.logger.warn('Hallucination detected', {
+ claims: grounding.unsupportedClaims,
+ });
+ }
+
+ return { answer, grounding, sources: results.map(r => r.key) };
+ },
+});
+
+export default ragAgent;
+```
+
+## Using in Evals
+
+For background quality monitoring, use LLM-as-judge in your eval files. Evals run asynchronously after the response is sent, so they don't slow down users:
+
+```typescript title="src/agent/qa-agent/eval.ts"
+import agent from './agent';
+import { generateObject } from 'ai';
+import { groq } from '@ai-sdk/groq';
+import { z } from 'zod';
+
+const HelpfulnessJudgment = z.object({
+ scores: z.object({
+ helpfulness: z.object({
+ score: z.number().min(0).max(1),
+ reason: z.string(),
+ }),
+ }),
+ checks: z.object({
+ answersQuestion: z.object({
+ passed: z.boolean(),
+ reason: z.string(),
+ }),
+ actionable: z.object({
+ passed: z.boolean(),
+ reason: z.string(),
+ }),
+ }),
+});
+
+export const helpfulnessEval = agent.createEval('helpfulness', {
+ description: 'Judges if responses are helpful and actionable',
+ handler: async (ctx, input, output) => {
+ const { object } = await generateObject({
+ model: groq('openai/gpt-oss-120b'),
+ schema: HelpfulnessJudgment,
+ prompt: `Evaluate this response.
+
+Question: ${input.question}
+Response: ${output.answer}
+
+SCORE:
+- helpfulness: How useful is this response? (0 = useless, 1 = extremely helpful)
+
+CHECKS:
+- answersQuestion: Does it directly answer what was asked?
+- actionable: Can the user act on this information?`,
+ });
+
+ // Combine score and checks into eval result
+ const allChecksPassed = object.checks.answersQuestion.passed && object.checks.actionable.passed;
+
+ return {
+ passed: allChecksPassed && object.scores.helpfulness.score >= 0.7,
+ score: object.scores.helpfulness.score,
+ reason: object.scores.helpfulness.reason,
+ metadata: {
+ checks: object.checks,
+ },
+ };
+ },
+});
+```
+
+See [Adding Evaluations](/Agents/evaluations) for more patterns including preset evals.
+
+## Structuring Judge Prompts
+
+Good judge prompts have clear structure:
+
+```typescript
+const judgePrompt = `You are evaluating a ${taskType} response.
+
+CONTEXT:
+${context}
+
+RESPONSE TO EVALUATE:
+${response}
+
+SCORING CRITERIA (0.0-1.0):
+- criterion1: What specifically to look for
+- criterion2: What specifically to look for
+
+CHECKS (pass/fail):
+- check1: Specific yes/no question
+- check2: Specific yes/no question
+
+Evaluate each criterion, then provide an overall assessment.`;
+```
+
+**Tips:**
+- Be explicit about the scale (0-1, not 1-10)
+- Define what each score level means
+- Make checks binary questions with clear answers
+- Include the original context/prompt for reference
+
+## Best Practices
+
+- **Use fast models for judging**: Groq, gpt-5-nano, or claude-haiku-4-5 work well
+- **Separate scores from checks**: Scores for gradients, checks for pass/fail
+- **Structure prompts clearly**: List criteria explicitly with descriptions
+- **Log low scores**: Track failures for debugging and model improvement
+- **Consider latency**: Use inline judging only when users need to see results
+
+## Cost Optimization
+
+LLM-as-judge adds model calls. Optimize by:
+
+- Using the fastest capable model (Groq for speed, nano models for cost)
+- Running evals asynchronously (default in Agentuity)
+- Sampling a percentage of requests in high-volume scenarios
+- Batching judgments when evaluating multiple items
+
+## Next Steps
+
+- [Adding Evaluations](/Agents/evaluations): Background quality monitoring with evals
+- [Using the AI SDK](/Agents/ai-sdk-integration): Model selection and configuration
+- [Vector Storage](/Services/Storage/vector): Build RAG systems to ground responses
diff --git a/content/Learn/Cookbook/Patterns/meta.json b/content/Learn/Cookbook/Patterns/meta.json
index 13f51da0..bab83255 100644
--- a/content/Learn/Cookbook/Patterns/meta.json
+++ b/content/Learn/Cookbook/Patterns/meta.json
@@ -4,6 +4,7 @@
"background-tasks",
"chat-with-history",
"cron-with-storage",
+ "llm-as-a-judge",
"server-utilities",
"product-search",
"tailwind-setup",
diff --git a/content/Learn/Cookbook/Patterns/server-utilities.mdx b/content/Learn/Cookbook/Patterns/server-utilities.mdx
index 90480c73..983f3577 100644
--- a/content/Learn/Cookbook/Patterns/server-utilities.mdx
+++ b/content/Learn/Cookbook/Patterns/server-utilities.mdx
@@ -1,6 +1,6 @@
---
title: SDK Utilities for External Apps
-description: Use storage, logging, error handling, and schema utilities from external backends like Next.js or Express
+description: Use storage, queues, logging, and error handling utilities from external backends like Next.js or Express
---
Use `@agentuity/server` and `@agentuity/core` utilities in external apps, scripts, or backends that integrate with Agentuity.
@@ -122,6 +122,146 @@ export async function GET(request: NextRequest) {
}
```
+## Queue Management
+
+Manage queues programmatically from external apps or scripts using `APIClient`:
+
+```typescript title="lib/agentuity-queues.ts"
+import { APIClient, createLogger, getServiceUrls } from '@agentuity/server';
+
+export const logger = createLogger('info');
+const urls = getServiceUrls(process.env.AGENTUITY_REGION!);
+
+export const client = new APIClient(
+ urls.catalyst,
+ logger,
+ process.env.AGENTUITY_SDK_KEY
+);
+```
+
+### Creating and Managing Queues
+
+```typescript
+import {
+ createQueue,
+ listQueues,
+ deleteQueue,
+ pauseQueue,
+ resumeQueue,
+} from '@agentuity/server';
+import { client } from '@/lib/agentuity-queues';
+
+// Create a worker queue
+const queue = await createQueue(client, {
+ name: 'order-processing',
+ queue_type: 'worker',
+ settings: {
+ default_max_retries: 5,
+ default_visibility_timeout_seconds: 60,
+ },
+});
+
+// List all queues
+const { queues } = await listQueues(client);
+
+// Pause and resume
+await pauseQueue(client, 'order-processing');
+await resumeQueue(client, 'order-processing');
+
+// Delete a queue
+await deleteQueue(client, 'old-queue');
+```
+
+### Dead Letter Queue Operations
+
+```typescript
+import {
+ listDeadLetterMessages,
+ replayDeadLetterMessage,
+ purgeDeadLetter,
+} from '@agentuity/server';
+import { client, logger } from '@/lib/agentuity-queues';
+
+// List failed messages
+const { messages } = await listDeadLetterMessages(client, 'order-processing');
+
+for (const msg of messages) {
+ logger.warn('Failed message', { id: msg.id, reason: msg.failure_reason });
+
+ // Replay back to the queue
+ await replayDeadLetterMessage(client, 'order-processing', msg.id);
+}
+
+// Purge all DLQ messages
+await purgeDeadLetter(client, 'order-processing');
+```
+
+### Webhook Destinations
+
+```typescript
+import { createDestination } from '@agentuity/server';
+import { client } from '@/lib/agentuity-queues';
+
+await createDestination(client, 'order-processing', {
+ destination_type: 'http',
+ config: {
+ url: 'https://api.example.com/webhook/orders',
+ method: 'POST',
+ headers: { 'X-API-Key': 'secret' },
+ timeout_ms: 30000,
+ retry_policy: {
+ max_attempts: 5,
+ initial_backoff_ms: 1000,
+ max_backoff_ms: 60000,
+ backoff_multiplier: 2.0,
+ },
+ },
+});
+```
+
+### HTTP Ingestion Sources
+
+```typescript
+import { createSource } from '@agentuity/server';
+import { client, logger } from '@/lib/agentuity-queues';
+
+const source = await createSource(client, 'webhook-queue', {
+ name: 'stripe-webhooks',
+ description: 'Receives Stripe payment events',
+ auth_type: 'header',
+ auth_value: 'Bearer whsec_...',
+});
+
+// External services POST to this URL
+logger.info('Source created', { url: source.url });
+```
+
+### Pull-Based Consumption
+
+For workers that pull and acknowledge messages:
+
+```typescript
+import { receiveMessage, ackMessage, nackMessage } from '@agentuity/server';
+import { client } from '@/lib/agentuity-queues';
+
+// Receive a message (blocks until available or timeout)
+const message = await receiveMessage(client, 'order-processing');
+
+if (message) {
+ try {
+ await processOrder(message.payload);
+ await ackMessage(client, 'order-processing', message.id);
+ } catch (error) {
+ // Message returns to queue for retry
+ await nackMessage(client, 'order-processing', message.id);
+ }
+}
+```
+
+
+For one-off queue management, use the CLI instead: `agentuity cloud queue create`, `agentuity cloud queue dlq`, etc. See [Queues](/Services/queues) for CLI commands.
+
+
## Alternative: HTTP Routes
If you want to centralize storage logic in your Agentuity project (for [middleware](/Routes/middleware), sharing across multiple apps, or avoiding SDK key distribution), use [HTTP routes](/Routes/http) instead.
@@ -182,7 +322,8 @@ export default router;
Add authentication middleware to protect storage endpoints:
```typescript title="src/api/sessions/route.ts"
-import { createRouter, createMiddleware } from '@agentuity/runtime';
+import { createRouter } from '@agentuity/runtime';
+import { createMiddleware } from 'hono/factory';
const router = createRouter();
@@ -330,6 +471,7 @@ const jsonSchema = toJSONSchema(schema);
## See Also
+- [Queues](/Services/queues): Queue concepts and CLI commands
- [HTTP Routes](/Routes/http): Route creation with `createRouter`
- [Route Middleware](/Routes/middleware): Authentication patterns
- [RPC Client](/Frontend/rpc-client): Typed client generation
diff --git a/content/Learn/Cookbook/overview.mdx b/content/Learn/Cookbook/overview.mdx
index 5e0c68f3..78b566c1 100644
--- a/content/Learn/Cookbook/overview.mdx
+++ b/content/Learn/Cookbook/overview.mdx
@@ -34,6 +34,7 @@ Copy-paste solutions for common tasks.
- [Background Tasks](/Learn/Cookbook/Patterns/background-tasks) — Use `waitUntil` for fire-and-forget work
- [Chat with Conversation History](/Learn/Cookbook/Patterns/chat-with-history) — Maintain conversation context using thread state
- [Cron with Storage](/Learn/Cookbook/Patterns/cron-with-storage) — Cache scheduled task results in KV for later retrieval
+- [LLM as a Judge](/Learn/Cookbook/Patterns/llm-as-a-judge) — Use LLMs to evaluate and score agent outputs
- [SDK Utilities for External Apps](/Learn/Cookbook/Patterns/server-utilities) — Use storage, logging, and error handling from Next.js or external backends
- [Product Search with Vector](/Learn/Cookbook/Patterns/product-search) — Semantic search with metadata filtering
- [Tailwind CSS Setup](/Learn/Cookbook/Patterns/tailwind-setup) — Add Tailwind styling to your frontend
diff --git a/content/Reference/CLI/ai-commands.mdx b/content/Reference/CLI/ai-commands.mdx
index dd0e0e25..0f1b4462 100644
--- a/content/Reference/CLI/ai-commands.mdx
+++ b/content/Reference/CLI/ai-commands.mdx
@@ -36,28 +36,6 @@ The schema includes:
- Parameter types and validation rules
- Command descriptions and examples
-## Agent Skills Generation
-
-Generate [Agent Skills](https://agentskills.io) documentation from the CLI schema:
-
-```bash
-# Generate SKILL.md files
-agentuity ai skills generate
-
-# Output to custom directory
-agentuity ai skills generate --output ./skills
-```
-
-This creates machine-readable skill definitions that enable coding agents (e.g., Claude Code, Cursor, Codex) to discover and use Agentuity CLI capabilities.
-
-**Generated files:**
-- `SKILL.md` files with YAML frontmatter for each command
-- `README.md` with a skill index
-
-
-In dev mode (`agentuity dev`), skills are automatically regenerated when the CLI version changes.
-
-
## Project Context Files
During development (`agentuity dev` or `bun run dev`), the CLI generates AGENTS.md files to help AI coding assistants (e.g., Claude Code, Codex, Cursor) understand Agentuity patterns:
@@ -111,23 +89,21 @@ These prompts are useful for:
Use the CLI to provide autocomplete, validation, and project configuration help:
```typescript
-// Get CLI capabilities for IDE integration
-const { execSync } = require('child_process');
+import { execSync } from 'child_process';
+// Get CLI capabilities for IDE integration
const capabilities = JSON.parse(
execSync('agentuity --json ai capabilities show').toString()
);
-
-// Use capabilities to build autocomplete, validation, etc.
```
```typescript
+import { execSync } from 'child_process';
+
// Get agentuity.json schema for configuration validation
const schema = JSON.parse(
execSync('agentuity --json ai schema generate').toString()
);
-
-// Use schema to validate project configuration files
```
### AI Coding Agent
@@ -140,6 +116,29 @@ agentuity ai prompt agent > /tmp/agentuity-context.md
cat /tmp/agentuity-context.md
```
+## OpenCode Plugin
+
+Install the Agentuity plugin for [OpenCode](https://opencode.ai) to enable AI-assisted development:
+
+```bash
+# Install the plugin
+agentuity ai opencode install
+
+# Remove the plugin
+agentuity ai opencode uninstall
+
+# Run a task in headless mode
+agentuity ai opencode run "Create an agent that validates email addresses"
+```
+
+The plugin provides specialized agents, slash commands, and MCP servers for Agentuity development. See [OpenCode Plugin](/Reference/CLI/opencode-plugin) for full documentation.
+
+## Cadence Commands
+
+Manage long-running AI tasks with the cadence system. Tasks persist across sessions, allowing complex operations to continue in the background.
+
+See [OpenCode Plugin: Cadence System](/Reference/CLI/opencode-plugin#cadence-system) for commands and integration details.
+
## Next Steps
- [Getting Started with the CLI](/Reference/CLI/getting-started): Install and authenticate
diff --git a/content/Reference/CLI/configuration.mdx b/content/Reference/CLI/configuration.mdx
index 812c850b..198d4efd 100644
--- a/content/Reference/CLI/configuration.mdx
+++ b/content/Reference/CLI/configuration.mdx
@@ -118,6 +118,58 @@ agentuity cloud secret import .env.secrets
**In agents:** Access secrets via `process.env.API_KEY`. Secrets are injected at runtime and never logged.
+## Organization-Level Configuration
+
+Set environment variables and secrets at the organization level to share them across all projects in that organization. Use the `--org` flag with any `env` or `secret` command.
+
+### Set Org-Level Variables
+
+```bash
+# Set using your default org
+agentuity cloud env set DATABASE_URL "postgresql://..." --org
+
+# Set for a specific org
+agentuity cloud env set DATABASE_URL "postgresql://..." --org org_abc123
+```
+
+### Set Org-Level Secrets
+
+```bash
+# Set shared secret for default org
+agentuity cloud secret set SHARED_API_KEY "sk_..." --org
+
+# Set for specific org
+agentuity cloud secret set SHARED_API_KEY "sk_..." --org org_abc123
+```
+
+### List Org-Level Values
+
+```bash
+# List org environment variables
+agentuity cloud env list --org
+
+# List org secrets
+agentuity cloud secret list --org
+```
+
+### Get/Delete Org-Level Values
+
+```bash
+# Get an org variable
+agentuity cloud env get DATABASE_URL --org
+
+# Delete an org secret
+agentuity cloud secret delete OLD_KEY --org
+```
+
+
+Organization-level values are inherited by all projects in that organization. Project-level values take precedence over organization-level values when both are set.
+
+
+
+Set a default organization with `agentuity auth org select` to avoid specifying `--org` on every command. See [Getting Started](/Reference/CLI/getting-started) for details.
+
+
## API Keys
Create and manage API keys for programmatic access to your project.
diff --git a/content/Reference/CLI/debugging.mdx b/content/Reference/CLI/debugging.mdx
index dc08a0f8..986a787a 100644
--- a/content/Reference/CLI/debugging.mdx
+++ b/content/Reference/CLI/debugging.mdx
@@ -36,7 +36,7 @@ agentuity cloud ssh --show
Add your SSH public key before connecting:
```bash
-agentuity auth ssh add ~/.ssh/id_rsa.pub
+agentuity auth ssh add --file ~/.ssh/id_rsa.pub
```
See [Getting Started](/Reference/CLI/getting-started) for authentication setup.
@@ -102,22 +102,16 @@ agentuity cloud agent list --project-id=proj_abc123xyz
# Get details for a specific agent
agentuity cloud agent get agent_abc123xyz
-
-# View agent input/output schema
-agentuity cloud agent schema agent_abc123xyz
```
**Agent details include:**
- Agent ID and name
- Description and metadata
- Associated routes
-- Schema definitions (input/output types)
Use agent inspection to:
- Verify deployed agent configuration
-- Debug schema mismatches
- Explore available agents in a project
-- Generate client code from schemas
## Session Logs
@@ -208,6 +202,33 @@ Use thread inspection to:
- Verify thread metadata
- Track thread lifecycle
+## Evaluation Commands
+
+Monitor and inspect evaluation runs to understand agent performance against test cases.
+
+List and inspect evaluations and evaluation runs:
+
+```bash
+# List all evaluations
+agentuity cloud eval list
+
+# Get details for a specific evaluation
+agentuity cloud eval get eval_abc123xyz
+
+# List evaluation runs
+agentuity cloud eval-run list
+
+# Get details for a specific run
+agentuity cloud eval-run get run_abc123xyz
+```
+
+Use evaluation commands to:
+- Monitor eval progress and results
+- Debug failing evaluations
+- Track evaluation history across deployments
+
+For more on configuring evaluations, see [Evaluations](/Agents/evaluations).
+
## Deployment Logs
Stream logs from a specific deployment:
@@ -286,6 +307,35 @@ agentuity --json cloud thread list --count=100 | \
jq -r '.[] | [.id, .user_data] | @csv' > threads.csv
```
+## Support Commands
+
+Generate diagnostic information for troubleshooting or support requests:
+
+```bash
+# Generate a support report (system info, project config, recent errors)
+agentuity support report
+
+# Export recent logs
+agentuity support logs
+agentuity support logs > debug-logs.txt
+agentuity support logs --limit 100
+
+# Show system information
+agentuity support system
+```
+
+**What's included:**
+
+| Command | Output |
+|---------|--------|
+| `support report` | CLI/runtime versions, project config (sanitized), recent errors, system info |
+| `support logs` | Recent log entries for local analysis |
+| `support system` | OS, Node.js/Bun versions, CLI version, memory/disk |
+
+
+Support reports automatically redact sensitive information like API keys, tokens, and environment variable values. Review the output before sharing.
+
+
## Next Steps
- [Managing Cloud Data](/Reference/CLI/data-management): Inspect storage, env vars, and secrets
diff --git a/content/Reference/CLI/deployment.mdx b/content/Reference/CLI/deployment.mdx
index 2ae513da..0b832f10 100644
--- a/content/Reference/CLI/deployment.mdx
+++ b/content/Reference/CLI/deployment.mdx
@@ -5,6 +5,19 @@ description: Deploy your agents to Agentuity Cloud with automatic infrastructure
Deploy your Agentuity project to the cloud with a single command. The platform handles infrastructure, scaling, and monitoring automatically.
+## Why Deploy to Agentuity?
+
+Deploying agents *should be* as easy as deploying a web app. But agents need long-running processes, persistent storage, sandbox environments, and observability built in. Setting this up yourself means stitching together containers, databases, secret management, and monitoring.
+
+Agentuity handles all of this automatically. You push code, and the platform provisions everything your agents need: compute, storage, networking, and observability. No infrastructure configuration, no Docker files, no Kubernetes.
+
+**What this gives you:**
+
+- **Single-command deployment**: `agentuity deploy` handles build, upload, and provisioning
+- **Automatic HTTPS**: Every deployment gets secure URLs with SSL certificates
+- **Built-in rollback**: Revert to any previous deployment instantly
+- **Zero infrastructure management**: No containers, orchestration, or scaling to configure
+
## Deploy Your Project
```bash
@@ -65,25 +78,6 @@ agentuity deploy
Both URLs are automatically provisioned with HTTPS. The project URL always routes to the active deployment.
-## Tagging Deployments
-
-Add tags to label deployments:
-
-```bash
-# Single tag
-agentuity deploy --tag staging
-
-# Multiple tags
-agentuity deploy --tag staging --tag hotfix-123
-```
-
-Tags appear in deployment lists and help organize versions:
-
-```bash
-agentuity cloud deployment list
-# Shows tags column for each deployment
-```
-
## Viewing Deployments
List recent deployments:
@@ -124,16 +118,19 @@ agentuity cloud deployment show dep_abc123xyz --project-id=proj_abc123
- Active status
- Tags and custom domains
- Cloud region
+- DNS records (for custom domain configuration)
- Git metadata (repo, commit, branch, PR)
- Build information (SDK version, runtime, platform)
```
-ID: dep_abc123xyz
-Project: proj_456def
-State: completed
-Active: Yes
-Created: 12/1/25, 3:45 PM
-Tags: staging, hotfix-123
+ID: dep_abc123xyz
+Project: proj_456def
+State: completed
+Active: Yes
+Created: 12/1/25, 3:45 PM
+Tags: staging, hotfix-123
+Domains: api.example.com
+DNS Records: api.example.com CNAME p1234567890.agentuity.cloud
Git Information
Repo: myorg/myapp
@@ -148,6 +145,10 @@ Build Information
Arch: x64
```
+
+When custom domains are configured, the `show` command displays the required DNS records for each domain. Use this to verify your CNAME configuration.
+
+
## Viewing Logs
Fetch logs for a deployment:
@@ -242,6 +243,25 @@ agentuity cloud deployment remove dep_abc123xyz --project-id=proj_abc123xyz
Removing a deployment permanently deletes it. You cannot rollback to a removed deployment.
+## Preview Environments
+
+Deploy pull requests to isolated preview environments for testing before merging. Enable this feature in the [Agentuity App](https://app-v1.agentuity.com):
+
+1. Navigate to your project's **Settings** page
+2. Under **GitHub**, find the **Preview Environments** toggle
+3. Enable "Deploy PRs to preview environments for this repository"
+
+Once enabled, every pull request to your connected repository automatically deploys to a unique preview URL. This allows you to:
+- Test changes in an isolated environment before merging
+- Share preview links with reviewers
+- Run integration tests against the preview deployment
+
+Preview environments are automatically cleaned up when the pull request is closed or merged.
+
+
+Preview environments require a connected GitHub repository. Connect your repo in **Project > Settings > GitHub**.
+
+
## Custom Domains
Configure custom domains in `agentuity.json`:
@@ -288,6 +308,10 @@ agentuity deploy
Deployment fails if DNS records are missing or incorrect. The CLI shows the required CNAME value in the error message.
+
+After adding a custom domain to your configuration, you must deploy to activate it. The domain becomes active only after a successful deployment.
+
+
### Multiple Domains
Custom domains replace the default URLs:
@@ -355,31 +379,60 @@ Variables are automatically:
Variables with suffixes like `_SECRET`, `_KEY`, `_TOKEN`, `_PASSWORD`, or `_PRIVATE` are automatically encrypted as secrets. All other variables are stored as regular environment variables.
+## Machine Management
+
+Machines are the compute instances running your deployments. View and manage them when debugging infrastructure issues.
+
+View and manage the machines running your deployments:
+
+```bash
+# List all machines for current project
+agentuity cloud machine list
+
+# Get details for a specific machine
+agentuity cloud machine get mach_abc123xyz
+
+# Delete a machine
+agentuity cloud machine delete mach_abc123xyz
+
+# List deployments running on a machine
+agentuity cloud machine deployments mach_abc123xyz
+```
+
+Machine details include status, region, resource allocation, and uptime. Use machine management to:
+- Monitor infrastructure health
+- Debug performance issues
+- Clean up unused machines
+
+
+Deleting a machine immediately terminates any deployments running on it. Only delete machines that are unused or when intentionally removing capacity.
+
+
## Deploy Options
| Option | Description |
|--------|-------------|
-| `--tag ` | Add deployment tag (repeatable) |
-| `-f, --force` | Force deployment without confirmation |
-| `--dry-run` | Simulate deployment without executing |
+| `-y, --confirm` | Skip all confirmation prompts (useful for CI/CD) |
+| `--dry-run` | Simulate deployment without executing (place before `deploy`) |
| `--log-level debug` | Show verbose output |
| `--project-id ` | Deploy specific project |
+| `--message ` | Message to associate with this build |
```bash
# Verbose deployment
agentuity deploy --log-level=debug
-# Multiple tags
-agentuity deploy --tag staging --tag v1.2.0
-
# Specific project
agentuity deploy --project-id=proj_abc123xyz
-# Simulate deployment
-agentuity deploy --dry-run
+# With a message
+agentuity deploy --message "Fix authentication bug"
+
+# Simulate deployment (dry run)
+agentuity --dry-run deploy
-# Force deployment
-agentuity deploy --force
+# Skip confirmation prompts (for CI/CD)
+agentuity deploy --confirm
```
## CI/CD Pipelines
diff --git a/content/Reference/CLI/development.mdx b/content/Reference/CLI/development.mdx
index c5f0b8e8..431b79a6 100644
--- a/content/Reference/CLI/development.mdx
+++ b/content/Reference/CLI/development.mdx
@@ -25,11 +25,11 @@ The server starts on port 3500 by default with:
|--------|---------|-------------|
| `--port` | 3500 (or `PORT` env) | TCP port for the dev server |
| `--local` | false | Use local services instead of cloud |
-| `--public` | true | Enable public URL tunneling |
| `--no-public` | - | Disable public URL tunneling |
-| `--interactive` | true | Enable interactive keyboard shortcuts |
-| `--watch ` | - | Additional paths to watch for changes (repeatable) |
-| `--profile ` | - | Load `.env.` in addition to `.env` |
+| `--no-interactive` | - | Disable interactive keyboard shortcuts |
+| `--inspect` | - | Enable Bun debugger for debugging |
+| `--inspect-wait` | - | Enable debugger and wait for connection before starting |
+| `--inspect-brk` | - | Enable debugger and break on first line |
The dev server respects the `PORT` environment variable. The `--port` flag takes precedence if both are set.
@@ -47,13 +47,38 @@ agentuity dev --no-public
# Non-interactive mode (useful for CI/CD)
agentuity dev --no-interactive
+```
+
+## Debugging with Inspector
-# Watch additional directories (e.g., local packages)
-agentuity dev --watch ../my-shared-lib/dist --watch ../other-package/src
+Use the inspector flags to debug your agents with Chrome DevTools or VS Code:
+
+```bash
+# Enable inspector (attach debugger anytime)
+agentuity dev --inspect
+
+# Wait for debugger before starting the server
+agentuity dev --inspect-wait
+
+# Break on first line of executed code
+agentuity dev --inspect-brk
```
-
-Use `--watch` when developing with local packages outside your project. Changes in watched paths trigger rebuilds just like changes in your source directory.
+Bun dynamically selects an available port and prints it to the console. Check the output for the debugger URL and port number.
+
+After starting, open `chrome://inspect` in Chrome or use VS Code's debugger to attach.
+
+
+Create a launch configuration that attaches to the running process. Update the port to match the one shown in the console output:
+
+```json
+{
+ "type": "node",
+ "request": "attach",
+ "name": "Attach to Agentuity",
+ "port": 6499
+}
+```
## Keyboard Shortcuts
@@ -104,53 +129,30 @@ const app = createApp({
See [Custom Storage](/Services/Storage/custom) for implementation details.
-## Profile-Specific Environment Variables
-
-Load different environment variables based on a profile name:
-
-```bash
-# Load .env.staging
-agentuity dev --profile staging
-
-# Load .env.testing
-agentuity dev --profile testing
-```
-
-Profile-specific files are loaded in addition to the base `.env` file. Variables in the profile file override those in `.env`.
+## Public URLs
-**Example setup:**
+Public URLs are enabled by default to share your local dev server or receive webhooks:
```bash
-# .env (base, always loaded)
-DATABASE_URL=postgres://localhost:5432/myapp
-LOG_LEVEL=info
-
-# .env.staging (loaded with --profile staging)
-DATABASE_URL=postgres://staging.example.com:5432/myapp
-API_BASE_URL=https://staging-api.example.com
+# Public URL enabled by default
+agentuity dev
-# .env.testing (loaded with --profile testing)
-DATABASE_URL=postgres://localhost:5432/myapp_test
-MOCK_EXTERNAL_APIS=true
+# Disable if not needed
+agentuity dev --no-public
```
-
-Use profiles to switch between configurations without editing files. Common profiles: `staging`, `testing`, `local`, `demo`.
-
+### Why Public URLs?
-## Public URLs
+Testing webhooks and external integrations during local development is painful. You either deploy constantly, configure port forwarding, or pay for third-party tunneling services. Each option adds friction to the development loop.
-Enable public URLs to share your local dev server or receive webhooks:
+Agentuity's Gravity network handles this automatically. When you run `agentuity dev`, your local server gets a public HTTPS URL instantly. No configuration, no separate tools, no accounts to manage. External services can reach your local agents as if they were already deployed.
-```bash
-# Public URL enabled by default
-agentuity dev
-
-# Or explicitly
-agentuity dev --public
-```
+**This means:**
-The `--public` flag creates a secure tunnel through Agentuity's edge network, giving your local server a public HTTPS URL for testing webhooks, sharing with teammates, or external integrations.
+- **Instant HTTPS URLs**: Automatic certificate generation
+- **Zero setup**: Works out of the box, no firewall rules or port forwarding
+- **Secure tunneling**: Encrypted connections through Agentuity's edge network
+- **Automatic reconnection**: Handles network interruptions gracefully
**Example output:**
```
diff --git a/content/Reference/CLI/getting-started.mdx b/content/Reference/CLI/getting-started.mdx
index 5643df71..64c6ecca 100644
--- a/content/Reference/CLI/getting-started.mdx
+++ b/content/Reference/CLI/getting-started.mdx
@@ -92,6 +92,38 @@ agentuity auth logout
agentuity logout
```
+## Preferences
+
+Set default organization and region to avoid specifying them on every command.
+
+### Organization Preferences
+
+```bash
+# Set default organization
+agentuity auth org select org_abc123
+
+# Show current default organization
+agentuity auth org current
+
+# Clear default organization
+agentuity auth org unselect
+```
+
+When a default organization is set, commands that support `--org` will use it automatically.
+
+### Region Preferences
+
+```bash
+# Set default region for deployments
+agentuity cloud region select us-west-1
+
+# Show current default region
+agentuity cloud region current
+
+# Clear default region
+agentuity cloud region unselect
+```
+
## Creating Projects
### Create a New Project
@@ -130,12 +162,17 @@ The agent uses the OpenAI SDK routed through Agentuity's AI Gateway. This means
### Project Creation Options
-- `--name `: Project name
-- `--dir `: Directory to create the project in
-- `--template `: Template to use (optional, defaults to "default")
-- `--no-install`: Skip dependency installation
-- `--no-build`: Skip initial build
-- `--no-register`: Don't register the project with Agentuity Cloud
+| Option | Description |
+|--------|-------------|
+| `--name ` | Project name |
+| `--dir ` | Directory to create the project in |
+| `--template ` | Template to use (optional, defaults to "default") |
+| `--no-install` | Skip dependency installation |
+| `--no-build` | Skip initial build |
+| `--no-register` | Don't register the project with Agentuity Cloud |
+| `--database ` | Database option: `skip`, `new`, or existing database name |
+| `--storage ` | Storage option: `skip`, `new`, or existing bucket name |
+| `--enable-auth` | Enable Agentuity Auth for the project |
Example:
@@ -146,6 +183,35 @@ agentuity project create \
--no-build
```
+### Headless Project Creation
+
+Create projects non-interactively for CI/CD pipelines and automation:
+
+```bash
+# Create with new database and storage
+agentuity project create \
+ --name my-agent \
+ --database new \
+ --storage new \
+ --enable-auth
+
+# Skip optional resources
+agentuity project create \
+ --name my-agent \
+ --database skip \
+ --storage skip
+
+# Use existing resources
+agentuity project create \
+ --name my-agent \
+ --database my-existing-db \
+ --storage my-bucket
+```
+
+
+The `--database` and `--storage` options require authentication. Run `agentuity login` first or the command will fail.
+
+
## Managing Projects
### List Projects
@@ -194,6 +260,27 @@ agentuity project delete --confirm
Project deletion is permanent and cannot be undone. Deployed agents and associated resources will be removed.
+### Import a Project
+
+Register an existing local Agentuity project with the cloud:
+
+```bash
+# Import current directory
+agentuity project import
+
+# Import from specific directory
+agentuity project import --dir ./my-existing-project
+
+# Validate without importing
+agentuity project import --validate-only
+```
+
+The import command verifies that the directory contains a valid Agentuity project (requires `@agentuity/runtime` dependency and either `agentuity.config.ts` or an `agentuity/` directory).
+
+
+When you run `agentuity dev` or `agentuity deploy` on an unregistered project while authenticated, the CLI will prompt you to import it automatically.
+
+
## Command Shortcuts
Several common commands have shortcuts that let you skip the subcommand prefix:
@@ -327,3 +414,4 @@ Now that you have the CLI installed and authenticated:
- [Local Development](/Reference/CLI/development): Run agents locally with hot reload
- [Deploying to the Cloud](/Reference/CLI/deployment): Deploy agents to Agentuity Cloud
- [Managing Cloud Data](/Reference/CLI/data-management): Inspect storage, env vars, and secrets
+- [OpenCode Plugin](/Reference/CLI/opencode-plugin): AI-assisted development with specialized agents
diff --git a/content/Reference/CLI/meta.json b/content/Reference/CLI/meta.json
index cdcea172..edc1da11 100644
--- a/content/Reference/CLI/meta.json
+++ b/content/Reference/CLI/meta.json
@@ -9,6 +9,7 @@
"sandbox",
"configuration",
"debugging",
- "ai-commands"
+ "ai-commands",
+ "opencode-plugin"
]
}
diff --git a/content/Reference/CLI/opencode-plugin.mdx b/content/Reference/CLI/opencode-plugin.mdx
new file mode 100644
index 00000000..4cf1f147
--- /dev/null
+++ b/content/Reference/CLI/opencode-plugin.mdx
@@ -0,0 +1,254 @@
+---
+title: OpenCode Plugin
+description: Install the Agentuity plugin for OpenCode to enable AI-assisted development with specialized agents
+---
+
+The OpenCode plugin adds Agentuity-aware AI agents and slash commands to [OpenCode](https://opencode.ai), providing intelligent assistance for building, deploying, and managing your agents.
+
+## Installation
+
+Install the plugin using the CLI:
+
+```bash
+agentuity ai opencode install
+```
+
+This configures OpenCode with:
+- Specialized AI agents for different development tasks
+- Slash commands for common Agentuity operations
+- MCP servers for library documentation and code search
+
+To remove the plugin:
+
+```bash
+agentuity ai opencode uninstall
+```
+
+## Specialized Agents
+
+The plugin provides various specialized agents, each focused on a specific aspect of development:
+
+| Agent | Role | When to Use |
+|-------|------|-------------|
+| **Lead** | Orchestrator | Automatically coordinates all work |
+| **Scout** | Explorer | Finding files, patterns, codebase analysis (read-only) |
+| **Builder** | Implementer | Interactive code changes, quick fixes, guided implementation |
+| **Architect** | Autonomous Implementer | Cadence mode, complex multi-file features, long-running tasks |
+| **Reviewer** | Code Reviewer | Reviewing changes, catching issues, suggesting fixes |
+| **Memory** | Context Manager | Storing/retrieving context, decisions, patterns across sessions |
+| **Expert** | Agentuity Specialist | CLI commands, cloud services, SDK questions |
+| **Planner** | Strategic Advisor | Complex architecture decisions, deep technical planning (read-only) |
+| **Runner** | Command Executor | Run lint/build/test/typecheck/format, returns structured summaries |
+
+OpenCode automatically routes tasks to the appropriate agent based on context.
+
+### Builder vs Architect
+
+| Aspect | Builder | Architect |
+|--------|---------|-----------|
+| **Mode** | Interactive | Autonomous |
+| **Best for** | Quick fixes, guided work | Cadence mode, complex features |
+| **Model** | Claude Opus 4.5 | GPT 5.2 Codex |
+| **Reasoning** | High | Maximum (xhigh) |
+| **Context** | Session-based | Checkpoint-based |
+
+Use **Builder** when working interactively, making quick changes, or needing guidance. Use **Architect** when running Cadence mode, implementing complex multi-file features, or needing autonomous execution with deep reasoning.
+
+### Model Configuration
+
+Override any agent's model in `opencode.json`. See [Agent Model Overrides](#agent-model-overrides) for full configuration options.
+
+Run `opencode models` to see all available models.
+
+
+Sensitive CLI commands are blocked by default: `agentuity cloud secrets`, `agentuity cloud apikey`, and `agentuity auth token`.
+
+
+## Slash Commands
+
+The plugin adds slash commands for common Agentuity tasks:
+
+| Command | Description |
+|---------|-------------|
+| `/agentuity-coder` | Run tasks with the full agent team (Lead orchestrates) |
+| `/agentuity-cadence` | Start a long-running autonomous loop |
+| `/agentuity-cloud` | Interact with any Agentuity cloud service |
+| `/agentuity-sandbox` | Run code in isolated sandbox environments |
+| `/agentuity-memory-save` | Save session context to memory |
+
+Use these commands by typing them directly in OpenCode:
+
+```
+/agentuity-coder Create a new agent that processes webhooks from Stripe
+```
+
+## MCP Servers
+
+The plugin configures two MCP servers that provide additional context to the AI agents:
+
+**context7**: Provides access to library documentation for common dependencies (AI SDK, Zod, Hono, etc.). Agents can look up API references and examples without leaving your editor.
+
+**grep_app**: Enables searching GitHub repositories for code examples and patterns. Useful when you need to see how others have implemented similar features.
+
+## Headless Mode
+
+Run Agentuity tasks from the command line without opening OpenCode:
+
+```bash
+agentuity ai opencode run "Create an agent that validates email addresses"
+```
+
+This executes the task using the appropriate agents and outputs the result. Useful for:
+- CI/CD pipelines that need AI-assisted code generation
+- Scripting repetitive development tasks
+- Batch processing multiple requests
+
+## Cadence System
+
+Cadence manages long-running AI tasks that continue in the background. This is useful for complex multi-step operations that take time to complete.
+
+### Managing Cadence Tasks
+
+```bash
+# List all cadence tasks
+agentuity ai cadence list
+
+# Check status of a specific task
+agentuity ai cadence status
+
+# Pause a running task
+agentuity ai cadence pause
+
+# Resume a paused task
+agentuity ai cadence resume
+
+# Stop a task completely
+agentuity ai cadence stop
+```
+
+Tasks persist across sessions, so you can start a complex operation, close your editor, and check on it later.
+
+## Background Agents
+
+Run agents in the background while continuing other work. Background agents execute asynchronously and notify you when complete.
+
+The plugin provides three tools for background task management:
+
+| Tool | Description |
+|------|-------------|
+| `background_task` | Launch an agent task in the background |
+| `background_output` | Retrieve the result of a completed task |
+| `background_cancel` | Cancel a running or pending background task |
+
+### Concurrency Control
+
+Background tasks are rate-limited to prevent overwhelming providers. Configure in your Agentuity CLI profile (`~/.config/agentuity/production.yaml`):
+
+```yaml
+coder:
+ background:
+ enabled: true
+ defaultConcurrency: 3 # Override the default of 1
+```
+
+| Option | Default | Description |
+|--------|---------|-------------|
+| `enabled` | `true` | Enable/disable background tasks |
+| `defaultConcurrency` | `1` | Default max concurrent tasks per model |
+| `staleTimeoutMs` | `1800000` | Timeout for stale tasks (30 minutes) |
+| `providerConcurrency` | `{}` | Per-provider concurrency limits |
+| `modelConcurrency` | `{}` | Per-model concurrency limits |
+
+## Tmux Integration
+
+When running inside tmux, background agents can spawn in separate panes for visual multi-agent execution.
+
+
+Tmux integration requires OpenCode to run with an HTTP server enabled. Start OpenCode with `opencode --port 4096`.
+
+
+Configure in your Agentuity CLI profile:
+
+```yaml
+coder:
+ tmux:
+ enabled: true
+ maxPanes: 6
+```
+
+| Option | Default | Description |
+|--------|---------|-------------|
+| `enabled` | `false` | Enable tmux pane spawning |
+| `maxPanes` | `4` | Max agent panes before rotating oldest out |
+| `mainPaneMinWidth` | `100` | Minimum width for main pane (columns) |
+| `agentPaneMinWidth` | `40` | Minimum width for agent panes (columns) |
+
+When enabled, agents spawn in a dedicated "Agents" window with a tiled grid layout. Oldest panes close automatically when `maxPanes` is reached.
+
+## Configuration
+
+Agent models are configured in `~/.config/opencode/opencode.json`. Plugin behavior settings go in your Agentuity CLI profile (`~/.config/agentuity/production.yaml`).
+
+### Agent Model Overrides
+
+Override any agent's model in `opencode.json`:
+
+```json
+{
+ "agent": {
+ "Agentuity Coder Builder": {
+ "model": "anthropic/claude-sonnet-4-5-20250514",
+ "temperature": 0.7
+ },
+ "Agentuity Coder Architect": {
+ "model": "openai/gpt-5.2-codex",
+ "reasoningEffort": "xhigh"
+ }
+ }
+}
+```
+
+| Option | Description |
+|--------|-------------|
+| `model` | Model identifier (e.g., `anthropic/claude-sonnet-4-5-20250514`) |
+| `temperature` | Number between 0-1 (lower = more deterministic) |
+| `reasoningEffort` | For OpenAI: `low`, `medium`, `high`, `xhigh` |
+| `variant` | For Anthropic: `low`, `medium`, `high`, `max` (extended thinking) |
+| `thinking` | For Anthropic: `{ "type": "enabled", "budgetTokens": 10000 }` |
+| `maxSteps` | Maximum tool use steps per turn |
+
+### MCP Server Configuration
+
+Add MCP servers for enhanced agent capabilities in `opencode.json`:
+
+```json
+{
+ "mcp": {
+ "context7": { "type": "remote", "url": "https://mcp.context7.com/mcp" },
+ "grep_app": { "type": "remote", "url": "https://mcp.grep.app" }
+ }
+}
+```
+
+| MCP | Purpose | Free Tier |
+|-----|---------|-----------|
+| **context7** | Library docs lookup | 500 req/month |
+| **grep_app** | GitHub code search | Unlimited |
+
+### Plugin Behavior Settings
+
+Configure plugin behavior in your Agentuity CLI profile (`~/.config/agentuity/production.yaml`):
+
+```yaml
+coder:
+ tmux:
+ enabled: true
+ background:
+ defaultConcurrency: 3
+```
+
+## Next Steps
+
+- [AI Commands](/Reference/CLI/ai-commands): Other AI-related CLI commands
+- [Local Development](/Reference/CLI/development): Run agents locally
+- [Creating Agents](/Agents/creating-agents): Build your first agent
diff --git a/content/Reference/CLI/storage.mdx b/content/Reference/CLI/storage.mdx
index a19c6764..76b64e84 100644
--- a/content/Reference/CLI/storage.mdx
+++ b/content/Reference/CLI/storage.mdx
@@ -13,6 +13,10 @@ All storage commands require the `cloud` prefix. For example: `agentuity cloud k
Inspect and manage key-value data organized into namespaces.
+
+KV storage is scoped to your organization, not individual projects. You can run KV commands from any directory without needing a project context.
+
+
### Interactive REPL
Start an interactive session for faster exploration:
diff --git a/content/Routes/sse.mdx b/content/Routes/sse.mdx
index 2841c1a7..8f78943a 100644
--- a/content/Routes/sse.mdx
+++ b/content/Routes/sse.mdx
@@ -229,6 +229,35 @@ Or with cURL:
curl -N https://your-project.agentuity.cloud/agent-name
```
+## Streaming LLM Responses
+
+Use SSE to stream AI SDK responses to clients:
+
+```typescript
+import { createRouter, sse } from '@agentuity/runtime';
+import { streamText } from 'ai';
+import { anthropic } from '@ai-sdk/anthropic';
+
+const router = createRouter();
+
+router.post('/chat', sse(async (c, stream) => {
+ const { message } = await c.req.json();
+
+ const result = streamText({
+ model: anthropic('claude-sonnet-4-5'),
+ prompt: message,
+ });
+
+ for await (const chunk of result.textStream) {
+ await stream.write(chunk);
+ }
+
+ stream.close();
+}));
+
+export default router;
+```
+
## SSE vs WebSocket
| Aspect | SSE | WebSocket |
diff --git a/content/Services/Observability/sessions-debugging.mdx b/content/Services/Observability/sessions-debugging.mdx
index 97006031..7d118ec1 100644
--- a/content/Services/Observability/sessions-debugging.mdx
+++ b/content/Services/Observability/sessions-debugging.mdx
@@ -5,6 +5,21 @@ description: Debug agents using session IDs, CLI commands, and trace timelines
Every request to your agents gets a unique session ID (`sess_...`). Sessions link logs, traces, and state, making them essential for debugging.
+## Why Sessions?
+
+Traditional observability tracks individual HTTP requests. That works for stateless web servers. Agents go beyond single requests—a conversation might span dozens of LLM calls, tool executions, and orchestration steps across multiple interactions.
+
+Without sessions, debugging becomes guesswork. You see individual logs but can't connect them. Was that error from the same conversation? What happened before it failed? Which user was affected?
+
+Agentuity tracks all of this automatically. Every run ties to a session. Every conversation thread is preserved. You get full context into what happened, when, and why—*without* writing tracking code.
+
+**In practice:**
+
+- **Unified tracing**: All logs, spans, and state from a single request are linked by session ID
+- **Conversation context**: Sessions group into threads, so you can trace multi-turn conversations
+- **Automatic correlation**: No manual tracking code, every call in a session is connected
+- **Session inspection**: Review what happened in a session to reproduce issues
+
## Sessions vs Threads
| Scope | Lifetime | ID Prefix | Use For |
diff --git a/content/Services/Sandbox/index.mdx b/content/Services/Sandbox/index.mdx
index 169368e6..f8156230 100644
--- a/content/Services/Sandbox/index.mdx
+++ b/content/Services/Sandbox/index.mdx
@@ -5,6 +5,21 @@ description: Run code in isolated, secure containers with configurable resources
Execute code in isolated Linux containers with configurable resource limits, network controls, and execution timeouts.
+## Why Sandboxes?
+
+Agents that reason about code need somewhere safe to execute it. Whether generating Python scripts, validating builds, or running user-provided code, you can't let arbitrary execution happen on your infrastructure.
+
+The pattern keeps repeating: spin up a secure environment, run code, tear it down. Without proper isolation, a single bad script could access sensitive data, exhaust resources, or compromise your systems.
+
+Agentuity sandboxes handle this automatically. Every execution runs in an isolated container with its own filesystem, configurable resource limits, and network controls. When execution completes, the environment is destroyed.
+
+**What this gives you:**
+
+- **Security by default**: Network disabled, filesystem isolated, resource limits enforced
+- **No infrastructure management**: Containers spin up and tear down automatically
+- **Multi-language support**: Run Python, Node.js, shell scripts, and more
+- **Consistent environments**: Use snapshots to get the same setup every time, with dependencies pre-installed
+
## Three Ways to Use Sandboxes
| Method | Best For |
@@ -75,6 +90,21 @@ Pre-configured testing runtimes:
Run `agentuity cloud sandbox runtime list` to see all available runtimes, or view them in the [Agentuity App](https://app-v1.agentuity.com) under **Services > Sandbox > Runtimes**.
+### Runtime Metadata
+
+Each runtime includes metadata for identification and resource planning:
+
+| Field | Description |
+|-------|-------------|
+| `description` | What the runtime provides |
+| `icon` | URL to runtime icon |
+| `brandColor` | Hex color for UI display |
+| `documentationUrl` | Link to runtime documentation |
+| `tags` | Categories like `language`, `ai-agent` |
+| `requirements` | Minimum memory, CPU, disk, and network needs |
+
+View runtime details with `agentuity cloud sandbox runtime list --json`.
+
## Snapshots
A snapshot captures the filesystem state of a sandbox. You create new sandboxes *from* a snapshot rather than running it directly.
@@ -156,6 +186,8 @@ The API is identical in both contexts.
| `resources.cpu` | CPU limit in millicores | `'500m'`, `'1000m'` |
| `resources.disk` | Disk space limit | `'1Gi'` |
| `network.enabled` | Allow outbound network | `true` (default: `false`) |
+| `network.port` | Port to expose to internet (1024-65535) | `3000` |
+| `projectId` | Associate sandbox with a project | `'proj_abc123'` |
| `timeout.idle` | Idle timeout before cleanup | `'10m'`, `'1h'` |
| `timeout.execution` | Max execution time per command | `'5m'`, `'30s'` |
| `dependencies` | Apt packages to install | `['python3', 'git']` |
diff --git a/content/Services/Sandbox/sdk-usage.mdx b/content/Services/Sandbox/sdk-usage.mdx
index c0d2c313..20889d2f 100644
--- a/content/Services/Sandbox/sdk-usage.mdx
+++ b/content/Services/Sandbox/sdk-usage.mdx
@@ -121,6 +121,49 @@ await sandbox.writeFiles([
]);
```
+### Exposing Ports
+
+Expose a port from the sandbox to make it accessible via a public URL:
+
+```typescript
+const sandbox = await ctx.sandbox.create({
+ network: {
+ enabled: true,
+ port: 3000, // Expose port 3000 (valid range: 1024-65535)
+ },
+ resources: { memory: '512Mi' },
+});
+
+// Start a web server inside the sandbox
+await sandbox.execute({ command: ['npm', 'run', 'serve'] });
+
+// Get the public URL
+const info = await ctx.sandbox.get(sandbox.id);
+if (info.url) {
+ ctx.logger.info('Server accessible at', { url: info.url, port: info.networkPort });
+}
+```
+
+
+Setting `network.port` requires `network.enabled: true`. The sandbox must have network access to expose ports.
+
+
+### Project Association
+
+Associate sandboxes with a project for organization and filtering:
+
+```typescript
+const sandbox = await ctx.sandbox.create({
+ projectId: 'proj_abc123', // Associate with project
+ resources: { memory: '512Mi' },
+});
+
+// List sandboxes by project
+const { sandboxes } = await ctx.sandbox.list({
+ projectId: 'proj_abc123',
+});
+```
+
### Reading Files
Read files from the sandbox as streams:
@@ -350,6 +393,8 @@ await ctx.sandbox.destroy('sbx_abc123');
| `resources.cpu` | `string` | CPU in millicores: `'500m'`, `'1000m'` |
| `resources.disk` | `string` | Disk limit: `'512Mi'`, `'2Gi'` |
| `network.enabled` | `boolean` | Enable outbound network (default: `false`) |
+| `network.port` | `number` | Port to expose to internet (1024-65535) |
+| `projectId` | `string` | Associate sandbox with a project |
| `timeout.idle` | `string` | Auto-destroy after idle: `'10m'`, `'1h'` |
| `timeout.execution` | `string` | Max command duration: `'30s'`, `'5m'` |
| `dependencies` | `string[]` | Apt packages: `['python3', 'git']` |
@@ -378,6 +423,9 @@ Returned by `sandbox.execute()`:
| `durationMs` | `number` | Execution duration in milliseconds |
| `stdoutStreamUrl` | `string` | URL to fetch stdout stream |
| `stderrStreamUrl` | `string` | URL to fetch stderr stream |
+| `cpuTimeMs` | `number` | CPU time consumed in milliseconds |
+| `memoryByteSec` | `number` | Memory usage in byte-seconds |
+| `networkEgressBytes` | `number` | Outbound network traffic in bytes |
### SandboxRunResult
@@ -388,6 +436,53 @@ Returned by `sandbox.execute()`:
| `durationMs` | `number` | Execution duration |
| `stdout` | `string` | Captured stdout (if available) |
| `stderr` | `string` | Captured stderr (if available) |
+| `cpuTimeMs` | `number` | CPU time consumed in milliseconds |
+| `memoryByteSec` | `number` | Memory usage in byte-seconds |
+| `networkEgressBytes` | `number` | Outbound network traffic in bytes |
+
+### SandboxInfo
+
+Returned by `ctx.sandbox.get()` and in list results:
+
+| Field | Type | Description |
+|-------|------|-------------|
+| `sandboxId` | `string` | Unique sandbox identifier |
+| `status` | `string` | `'idle'`, `'busy'`, `'terminated'` |
+| `createdAt` | `string` | ISO timestamp |
+| `snapshotId` | `string` | Source snapshot (if created from snapshot) |
+| `networkPort` | `number` | Port exposed from sandbox (if configured) |
+| `url` | `string` | Public URL (when port is configured) |
+| `user` | `SandboxUserInfo` | User who created the sandbox |
+| `agent` | `SandboxAgentInfo` | Agent that created the sandbox |
+| `project` | `SandboxProjectInfo` | Associated project |
+| `org` | `SandboxOrgInfo` | Organization (always present) |
+
+Access context information from sandbox info:
+
+```typescript
+const info = await ctx.sandbox.get('sbx_abc123');
+
+// Organization is always present
+ctx.logger.info('Organization', { id: info.org.id, name: info.org.name });
+
+// User info (when created by a user)
+if (info.user) {
+ ctx.logger.info('Created by', {
+ userId: info.user.id,
+ name: `${info.user.firstName} ${info.user.lastName}`,
+ });
+}
+
+// Agent info (when created by an agent)
+if (info.agent) {
+ ctx.logger.info('Agent', { id: info.agent.id, name: info.agent.name });
+}
+
+// Project info (when associated with a project)
+if (info.project) {
+ ctx.logger.info('Project', { id: info.project.id, name: info.project.name });
+}
+```
## Best Practices
diff --git a/content/Services/Storage/database.mdx b/content/Services/Storage/database.mdx
index f21fc7eb..a33a3b94 100644
--- a/content/Services/Storage/database.mdx
+++ b/content/Services/Storage/database.mdx
@@ -19,6 +19,20 @@ Database storage provides relational database capabilities using [Bun's native S
When you create a database with `agentuity cloud db create`, the `DATABASE_URL` is automatically added to your `.env` file. During deployment, credentials are injected automatically.
+## Creating a Database
+
+Create a database from the CLI with optional name and description:
+
+```bash
+# Create with default settings
+agentuity cloud db create
+
+# Create with name and description
+agentuity cloud db create --name "users-db" --description "Primary user data store"
+```
+
+The name and description help identify databases in the dashboard when managing multiple databases.
+
Database access uses Bun's native SQL API directly. The same `import { sql } from "bun"` code works identically in agents, routes, and standalone scripts with no wrapper needed.
@@ -151,6 +165,125 @@ const sqlite = new SQL("sqlite://data/app.db");
For complete API documentation including dynamic queries, bulk inserts, savepoints, array operations, error handling, and connection pooling, see the [Bun SQL documentation](https://bun.com/docs/runtime/sql).
+## Resilient Postgres Client
+
+The `@agentuity/postgres` package provides a connection-resilient PostgreSQL client built for serverless environments. It handles connection drops, cold starts, and automatic reconnection.
+
+```bash
+bun add @agentuity/postgres
+```
+
+```typescript
+import { postgres } from '@agentuity/postgres';
+
+// Create client (uses DATABASE_URL by default)
+const sql = postgres();
+
+// Queries automatically retry on connection errors
+const users = await sql`SELECT * FROM users WHERE active = ${true}`;
+
+// Transactions
+const tx = await sql.begin();
+await tx`INSERT INTO users (name) VALUES (${'Alice'})`;
+await tx.commit();
+```
+
+### Configuration
+
+```typescript
+import { postgres } from '@agentuity/postgres';
+
+const sql = postgres({
+ url: 'postgres://user:pass@localhost:5432/mydb',
+
+ // Reconnection
+ reconnect: {
+ maxAttempts: 10,
+ initialDelayMs: 100,
+ maxDelayMs: 30000,
+ },
+
+ // Callbacks
+ onclose: (error) => console.log('Connection closed', error),
+ onreconnected: () => console.log('Reconnected!'),
+});
+```
+
+**Key features:**
+
+- **Automatic reconnection**: Exponential backoff with jitter on connection drops
+- **Query retry**: Retries queries on retryable errors (connection reset, timeout)
+- **Transaction support**: Full transaction and savepoint support with `sql.begin()`
+- **Graceful shutdown**: Skips reconnection on SIGTERM/SIGINT for clean process exit
+- **Connection stats**: Track connection health via `sql.stats`
+
+
+Use `@agentuity/postgres` when you need resilient connections in serverless environments, especially with Neon or similar providers that have cold start latency.
+
+
+## Drizzle ORM Integration
+
+The `@agentuity/drizzle` package provides type-safe database access with [Drizzle ORM](https://orm.drizzle.team/) and automatic reconnection.
+
+```bash
+bun add @agentuity/drizzle
+```
+
+```typescript
+import { createPostgresDrizzle, eq } from '@agentuity/drizzle';
+import * as schema from './schema';
+
+// Create database instance (uses DATABASE_URL by default)
+const { db, client, close } = createPostgresDrizzle({ schema });
+
+// Type-safe queries with schema inference
+const activeUsers = await db.select().from(schema.users).where(eq(schema.users.active, true));
+
+// Access connection stats via the underlying client
+console.log(client.stats);
+
+// Clean up when done
+await close();
+```
+
+**Key features:**
+
+- **Type-safe queries**: Full TypeScript inference from your Drizzle schema
+- **Resilient connection**: Built on `@agentuity/postgres` for automatic reconnection
+- **Convenient re-exports**: Common Drizzle utilities (`eq`, `and`, `pgTable`, etc.) available from a single import
+- **Auth integration**: Works with `@agentuity/auth` via `drizzleAdapter`
+
+```typescript
+import { createPostgresDrizzle, eq } from '@agentuity/drizzle';
+import * as schema from './schema';
+
+const { db, close } = createPostgresDrizzle({
+ schema,
+ logger: true, // Enable query logging
+ reconnect: {
+ maxAttempts: 5,
+ initialDelayMs: 100,
+ },
+ onReconnected: () => console.log('Reconnected to database'),
+});
+
+// Drizzle infers the joined type
+const usersWithPosts = await db
+ .select()
+ .from(schema.users)
+ .leftJoin(schema.posts, eq(schema.users.id, schema.posts.authorId));
+
+// Insert with returning
+const [newUser] = await db
+ .insert(schema.users)
+ .values({ name: 'Alice', email: 'alice@example.com' })
+ .returning();
+```
+
+
+Define your schema in a separate file and use `drizzle-kit` for migrations. See the [Drizzle documentation](https://orm.drizzle.team/docs/sql-schema-declaration) for schema syntax.
+
+
## Next Steps
- [Key-Value Storage](/Services/Storage/key-value): Fast caching and session data
diff --git a/content/Services/Storage/durable-streams.mdx b/content/Services/Storage/durable-streams.mdx
index 413836c4..b5d51ac8 100644
--- a/content/Services/Storage/durable-streams.mdx
+++ b/content/Services/Storage/durable-streams.mdx
@@ -3,7 +3,22 @@ title: Durable Streams
description: Streaming storage for large exports, audit logs, and real-time data
---
-Durable streams provide streaming storage for large data exports, audit logs, and real-time processing. Streams follow a **write-once, read-many** pattern: once data is written and the stream is closed, the content is immutable and can be accessed via a permanent public URL.
+Durable streams provide streaming storage for large data exports, audit logs, and real-time processing. Streams follow a **write-once, read-many** pattern: once data is written and the stream is closed, the content is immutable and accessible via URL until deleted.
+
+## Why Durable Streams?
+
+WebSocket and SSE connections are straightforward to set up, but they're fragile in practice. Tabs get suspended, networks disconnect, pages get refreshed. When the connection drops, any in-flight data is lost—unless you build custom reconnection logic on top.
+
+This becomes a real problem when you're streaming LLM responses. Token streaming is often the primary UI for chat applications, and agentic apps stream tool outputs and progress events over long-running sessions. If someone refreshes mid-generation, you're faced with two bad options: re-run an expensive inference call, or lose the response entirely.
+
+Durable streams solve this by decoupling the data from the connection. Instead of streaming directly to the client, you write to persistent storage and return a URL. The stream continues writing in the background regardless of what happens on the client side.
+
+**What this gives you:**
+
+- **Refresh-safe**: If someone refreshes the page mid-stream, the URL still works and the content is preserved. The expensive work you already did isn't wasted.
+- **Background processing**: Return the stream URL immediately and write data asynchronously with `ctx.waitUntil()`. Your handler responds fast while the stream continues writing.
+- **Shareable URLs**: A stream is just a URL. You can share it, open it on another device, or have multiple viewers access the same output.
+- **Durable artifacts**: Once closed, streams are immutable and remain accessible via their public URL. Useful for audit logs, exports, and generated reports.
## When to Use Durable Streams
@@ -46,6 +61,7 @@ const agent = createAgent('StreamCreator', {
contentType: 'text/csv',
compress: true, // optional gzip compression
metadata: { userId: input.userId },
+ ttl: 86400 * 7, // expires in 7 days
});
// Stream is ready immediately
@@ -63,12 +79,27 @@ const agent = createAgent('StreamCreator', {
- `contentType`: MIME type for the stream content (e.g., `text/csv`, `application/json`)
- `compress`: Enable gzip compression for smaller storage and faster transfers
- `metadata`: Key-value pairs for tracking stream context (user IDs, timestamps, etc.)
+- `ttl`: Time-to-live in seconds (see TTL semantics below)
+
+**TTL semantics:**
+
+| Value | Behavior |
+|-------|----------|
+| `undefined` | Streams expire after 30 days (default) |
+| `null` or `0` | Streams never expire |
+| `>= 60` | Custom TTL in seconds (minimum 60 seconds, maximum 90 days) |
+
+
+TTL is enforced only in cloud deployments. During local development, streams persist indefinitely regardless of the TTL value. The `expiresAt` field will not be populated in local stream metadata.
+
## Writing Data
Write data incrementally, then close the stream:
```typescript
+import { createAgent } from '@agentuity/runtime';
+
const agent = createAgent('CSVExporter', {
handler: async (ctx, input) => {
const stream = await ctx.stream.create('export', {
@@ -104,6 +135,8 @@ Streams must be closed manually with `stream.close()`. They do not auto-close. F
Use `ctx.waitUntil()` to write data in the background while returning immediately:
```typescript
+import { createAgent } from '@agentuity/runtime';
+
const agent = createAgent('BackgroundExporter', {
handler: async (ctx, input) => {
const stream = await ctx.stream.create('report', {
@@ -133,11 +166,13 @@ const agent = createAgent('BackgroundExporter', {
### Listing Streams
```typescript
+import { createAgent } from '@agentuity/runtime';
+
const agent = createAgent('StreamManager', {
handler: async (ctx, input) => {
// List streams with optional filtering
const result = await ctx.stream.list({
- name: 'export', // filter by stream name
+ namespace: 'export', // filter by stream namespace
metadata: { userId: input.userId }, // filter by metadata
limit: 100, // max 1000, default 100
offset: 0, // pagination offset
@@ -156,11 +191,17 @@ const agent = createAgent('StreamManager', {
### Reading Stream Content
```typescript
+import { createAgent } from '@agentuity/runtime';
+
const agent = createAgent('StreamReader', {
handler: async (ctx, input) => {
// Get stream metadata by ID
const info = await ctx.stream.get(input.streamId);
- ctx.logger.info('Stream info', { name: info.name, sizeBytes: info.sizeBytes });
+ ctx.logger.info('Stream info', {
+ namespace: info.namespace,
+ sizeBytes: info.sizeBytes,
+ expiresAt: info.expiresAt, // ISO timestamp when stream expires
+ });
// Download stream content as ReadableStream
const content = await ctx.stream.download(input.streamId);
@@ -183,6 +224,8 @@ const agent = createAgent('StreamReader', {
### Deleting Streams
```typescript
+import { createAgent } from '@agentuity/runtime';
+
const agent = createAgent('StreamCleaner', {
handler: async (ctx, input) => {
await ctx.stream.delete(input.streamId);
@@ -250,6 +293,7 @@ const agent = createAgent('DualStreamWriter', {
Buffer and evaluate content in real-time using an LLM-as-a-judge pattern:
```typescript
+import { createAgent } from '@agentuity/runtime';
import { generateObject, streamText } from 'ai';
import { openai } from '@ai-sdk/openai';
import { groq } from '@ai-sdk/groq';
@@ -302,7 +346,7 @@ const agent = createAgent('ModeratedStreamer', {
// Use Groq for low-latency moderation
async function moderateContent(text: string): Promise {
const { object } = await generateObject({
- model: groq('openai/gpt-oss-20b'),
+ model: groq('openai/gpt-oss-120b'),
schema: s.object({
safe: s.boolean(),
reason: s.optional(s.string()),
diff --git a/content/Services/Storage/key-value.mdx b/content/Services/Storage/key-value.mdx
index 577cad1f..2b9df669 100644
--- a/content/Services/Storage/key-value.mdx
+++ b/content/Services/Storage/key-value.mdx
@@ -44,16 +44,21 @@ import { createAgent } from '@agentuity/runtime';
const agent = createAgent('CacheManager', {
handler: async (ctx, input) => {
- // Store with optional TTL (minimum 60 seconds)
+ // Store with custom TTL (minimum 60 seconds, maximum 90 days)
await ctx.kv.set('cache', 'api-response', responseData, {
ttl: 3600, // expires in 1 hour
contentType: 'application/json',
});
- // Store without TTL (persists indefinitely)
+ // Store with default TTL (7 days)
+ await ctx.kv.set('cache', 'user-prefs', { theme: 'dark' });
+
+ // Store with no expiration (persists indefinitely)
await ctx.kv.set('config', 'feature-flags', {
darkMode: true,
betaFeatures: false,
+ }, {
+ ttl: null, // never expires (0 also works)
});
return { success: true };
@@ -64,12 +69,17 @@ const agent = createAgent('CacheManager', {
### Retrieving Data
```typescript
+import { createAgent } from '@agentuity/runtime';
+
const agent = createAgent('CacheRetriever', {
handler: async (ctx, input) => {
const result = await ctx.kv.get('cache', 'api-response');
if (result.exists) {
- ctx.logger.info('Cache hit', { contentType: result.contentType });
+ ctx.logger.info('Cache hit', {
+ contentType: result.contentType,
+ expiresAt: result.expiresAt, // ISO timestamp when key expires (if TTL set)
+ });
return { data: result.data };
}
@@ -82,6 +92,8 @@ const agent = createAgent('CacheRetriever', {
### Deleting Data
```typescript
+import { createAgent } from '@agentuity/runtime';
+
const agent = createAgent('SessionCleaner', {
handler: async (ctx, input) => {
await ctx.kv.delete('sessions', input.sessionId);
@@ -95,6 +107,7 @@ const agent = createAgent('SessionCleaner', {
Use generics for type-safe data access:
```typescript
+import { createAgent } from '@agentuity/runtime';
import { type } from 'arktype';
const UserPreferences = type({
@@ -123,6 +136,8 @@ const agent = createAgent('PreferenceLoader', {
## Additional Methods
```typescript
+import { createAgent } from '@agentuity/runtime';
+
const agent = createAgent('StorageExplorer', {
handler: async (ctx, input) => {
// Search keys by keyword (returns keys with metadata)
@@ -150,10 +165,19 @@ const agent = createAgent('StorageExplorer', {
Create and delete namespaces programmatically:
```typescript
+import { createAgent } from '@agentuity/runtime';
+
const agent = createAgent('NamespaceManager', {
handler: async (ctx, input) => {
- // Create a new namespace
- await ctx.kv.createNamespace('tenant-123');
+ // Create a namespace with default TTL for all keys
+ await ctx.kv.createNamespace('cache', {
+ defaultTTLSeconds: 3600, // all keys expire in 1 hour by default
+ });
+
+ // Create a namespace with no expiration
+ await ctx.kv.createNamespace('config', {
+ defaultTTLSeconds: 0, // keys never expire
+ });
// Delete a namespace (removes all keys)
await ctx.kv.deleteNamespace('old-cache');
@@ -163,13 +187,29 @@ const agent = createAgent('NamespaceManager', {
});
```
+**TTL semantics:**
+
+| Value | Behavior |
+|-------|----------|
+| `undefined` | Keys expire after 7 days (default) |
+| `null` or `0` | Keys never expire |
+| `>= 60` | Custom TTL in seconds (minimum 60 seconds, maximum 90 days) |
+
+
+The minimum TTL is 60 seconds. The maximum is 90 days (7,776,000 seconds). Values outside this range will be rejected.
+
+
+
+When a key is read with less than 50% of its TTL remaining, the expiration is automatically extended. This keeps frequently-accessed data alive without manual renewal.
+
+
`deleteNamespace()` permanently removes the namespace and all its keys. This operation cannot be undone.
## TTL Strategy
-Keys persist indefinitely by default. Use TTL for temporary data:
+Keys expire after 7 days by default unless a namespace-level or per-key TTL is set. Use TTL for temporary data:
| Data Type | Suggested TTL |
|-----------|---------------|
@@ -179,6 +219,8 @@ Keys persist indefinitely by default. Use TTL for temporary data:
| Feature flags | No TTL (persistent) |
```typescript
+import { createAgent } from '@agentuity/runtime';
+
interface UserSession {
userId: string;
email: string;
diff --git a/content/Services/Storage/vector.mdx b/content/Services/Storage/vector.mdx
index 97ebb43e..b3fc2be8 100644
--- a/content/Services/Storage/vector.mdx
+++ b/content/Services/Storage/vector.mdx
@@ -44,11 +44,13 @@ const agent = createAgent('KnowledgeLoader', {
key: 'doc-1',
document: 'Agentuity is an agent-native cloud platform',
metadata: { category: 'platform', source: 'docs' },
+ ttl: 86400 * 7, // expires in 7 days
},
{
key: 'doc-2',
document: 'Vector storage enables semantic search capabilities',
metadata: { category: 'features', source: 'docs' },
+ // No TTL specified: uses 30-day default
}
);
@@ -58,6 +60,18 @@ const agent = createAgent('KnowledgeLoader', {
});
```
+**TTL semantics:**
+
+| Value | Behavior |
+|-------|----------|
+| `undefined` | Vectors expire after 30 days (default) |
+| `null` or `0` | Vectors never expire |
+| `>= 60` | Custom TTL in seconds (minimum 60 seconds, maximum 90 days) |
+
+
+TTL is enforced only in cloud deployments. During local development, vectors persist indefinitely regardless of the TTL value. The `expiresAt` field will not be populated in local search results.
+
+
**With pre-computed embeddings:**
```typescript
@@ -65,6 +79,7 @@ await ctx.vector.upsert('custom-embeddings', {
key: 'embedding-1',
embeddings: [0.1, 0.2, 0.3, 0.4, ...],
metadata: { source: 'external' },
+ ttl: null, // never expires
});
```
@@ -74,6 +89,8 @@ await ctx.vector.upsert('custom-embeddings', {
Find semantically similar documents:
```typescript
+import { createAgent } from '@agentuity/runtime';
+
const agent = createAgent('SemanticSearch', {
handler: async (ctx, input) => {
const results = await ctx.vector.search('knowledge-base', {
@@ -83,12 +100,13 @@ const agent = createAgent('SemanticSearch', {
metadata: { category: 'platform' }, // filter by metadata
});
- // Each result includes: id, key, similarity, metadata
+ // Each result includes: id, key, similarity, metadata, expiresAt
return {
results: results.map(r => ({
key: r.key,
similarity: r.similarity,
title: r.metadata?.title,
+ expiresAt: r.expiresAt, // ISO timestamp when vector expires
})),
};
},
@@ -102,6 +120,8 @@ const agent = createAgent('SemanticSearch', {
Retrieve a specific vector by key without similarity search:
```typescript
+import { createAgent } from '@agentuity/runtime';
+
const agent = createAgent('DocumentRetriever', {
handler: async (ctx, input) => {
const result = await ctx.vector.get('knowledge-base', 'doc-1');
@@ -124,6 +144,8 @@ const agent = createAgent('DocumentRetriever', {
Retrieve multiple vectors efficiently:
```typescript
+import { createAgent } from '@agentuity/runtime';
+
const agent = createAgent('BatchRetriever', {
handler: async (ctx, input) => {
const keys = ['doc-1', 'doc-2', 'doc-3'];
@@ -143,6 +165,8 @@ const agent = createAgent('BatchRetriever', {
### exists() - Check Namespace
+Check if a namespace contains any vectors:
+
```typescript
const hasData = await ctx.vector.exists('knowledge-base');
if (!hasData) {
@@ -150,6 +174,10 @@ if (!hasData) {
}
```
+
+`exists()` returns `false` for namespaces that exist but contain no vectors. Use this to verify your knowledge base has been populated with data before searching.
+
+
## Deleting Vectors
```typescript
@@ -165,6 +193,8 @@ const count = await ctx.vector.delete('knowledge-base', 'doc-1', 'doc-2', 'doc-3
Use generics for type-safe metadata:
```typescript
+import { createAgent } from '@agentuity/runtime';
+
interface DocumentMetadata {
title: string;
category: 'guide' | 'api' | 'tutorial';
diff --git a/content/Services/queues.mdx b/content/Services/queues.mdx
new file mode 100644
index 00000000..deb4fb82
--- /dev/null
+++ b/content/Services/queues.mdx
@@ -0,0 +1,327 @@
+---
+title: Queues
+description: Publish messages for async processing, webhooks, and event-driven workflows
+---
+
+Queues enable asynchronous message processing for background tasks, webhooks, and event-driven workflows. Publish messages from agents and consume them with workers or webhook destinations.
+
+## When to Use Queues
+
+| Pattern | Best For |
+|---------|----------|
+| **Queues** | Background jobs, webhooks, event-driven processing, decoupled services |
+| [Durable Streams](/Services/Storage/durable-streams) | Large exports, audit logs, streaming data |
+| [Key-Value](/Services/Storage/key-value) | Fast lookups, caching, configuration |
+
+**Use queues when you need to:**
+- Process tasks asynchronously (email sending, report generation)
+- Decouple services with reliable message delivery
+- Deliver webhooks to external endpoints
+- Handle bursty workloads with rate limiting
+- Retry failed operations with exponential backoff
+
+## Access Patterns
+
+| Context | Access | Details |
+|---------|--------|---------|
+| Agents | `ctx.queue` | See examples below |
+| Routes | `c.var.queue` | See [Using in Routes](#using-in-routes) |
+| Standalone | `createAgentContext()` | See [Standalone Usage](#standalone-usage) |
+
+
+The Queue API is identical in all contexts. `ctx.queue.publish()` and `c.var.queue.publish()` work the same way.
+
+
+## Queue Types
+
+| Type | Behavior |
+|------|----------|
+| `worker` | Each message is processed by exactly one consumer. Use for background jobs. |
+| `pubsub` | Messages are broadcast to all subscribers. Use for event notifications. |
+
+## Publishing Messages
+
+Publish messages from agents using `ctx.queue.publish()`:
+
+```typescript
+import { createAgent } from '@agentuity/runtime';
+
+const agent = createAgent('OrderProcessor', {
+ handler: async (ctx, input) => {
+ // Queue an email to be sent asynchronously
+ const result = await ctx.queue.publish('email-queue', {
+ to: input.customerEmail,
+ subject: 'Order Confirmed',
+ orderId: input.orderId,
+ });
+
+ ctx.logger.info('Email queued', { messageId: result.id });
+
+ return { success: true, messageId: result.id };
+ },
+});
+```
+
+### Publish Options
+
+```typescript
+const agent = createAgent('TaskScheduler', {
+ handler: async (ctx, input) => {
+ await ctx.queue.publish('task-queue', input.task, {
+ // Attach metadata for filtering or routing
+ metadata: { priority: 'high', region: 'us-west' },
+
+ // Guarantee ordering for messages with the same key
+ partitionKey: input.customerId,
+
+ // Prevent duplicate messages
+ idempotencyKey: `task-${input.taskId}-v1`,
+
+ // Auto-expire after 1 hour
+ ttl: 3600,
+ });
+
+ return { queued: true };
+ },
+});
+```
+
+| Option | Description |
+|--------|-------------|
+| `metadata` | Key-value pairs for routing or filtering |
+| `partitionKey` | Messages with the same key are processed in order |
+| `idempotencyKey` | Prevents duplicate messages if the same key is published again |
+| `ttl` | Time-to-live in seconds before the message expires |
+
+### Publish Result
+
+```typescript
+interface QueuePublishResult {
+ id: string; // Unique message ID (msg_...)
+ offset: number; // Sequential position in the queue
+ publishedAt: string; // ISO 8601 timestamp
+}
+```
+
+### Synchronous Publishing
+
+Use `sync: true` when you need to wait for the message to be persisted before returning:
+
+```typescript
+import { createAgent } from '@agentuity/runtime';
+
+const agent = createAgent('CriticalProcessor', {
+ handler: async (ctx, input) => {
+ // Wait for message to be persisted
+ const result = await ctx.queue.publish('critical-tasks', {
+ taskId: input.taskId,
+ payload: input.data,
+ }, {
+ sync: true,
+ });
+
+ ctx.logger.info('Task queued synchronously', { taskId: input.taskId });
+ return { status: 'queued', messageId: result.id };
+ },
+});
+```
+
+
+Synchronous publishing blocks until the message is persisted. Use it only when you need confirmation that the message was accepted. For most use cases, async publishing is faster and more resilient.
+
+
+## Using in Routes
+
+Routes have the same queue access via `c.var.queue`:
+
+```typescript
+import { createRouter } from '@agentuity/runtime';
+
+const router = createRouter();
+
+router.post('/webhook/stripe', async (c) => {
+ const event = await c.req.json();
+
+ // Queue webhook for async processing
+ await c.var.queue.publish('stripe-webhooks', {
+ type: event.type,
+ data: event.data,
+ });
+
+ // Return 200 immediately (Stripe expects fast responses)
+ return c.json({ received: true });
+});
+
+export default router;
+```
+
+
+Use queues for webhooks that need quick acknowledgment. Return 200 immediately, then process the payload asynchronously.
+
+
+## Standalone Usage
+
+Use queues from background jobs with `createAgentContext()`:
+
+```typescript
+import { createApp, createAgentContext } from '@agentuity/runtime';
+
+await createApp();
+
+// Scheduled job to send daily reports
+async function sendDailyReports() {
+ const ctx = createAgentContext({ trigger: 'cron' });
+
+ await ctx.invoke(async () => {
+ const users = await getActiveUsers();
+
+ for (const user of users) {
+ await ctx.queue.publish('email-reports', {
+ userId: user.id,
+ reportType: 'daily',
+ });
+ }
+
+ ctx.logger.info('Queued daily reports', { count: users.length });
+ });
+}
+```
+
+See [Running Agents Without HTTP](/Agents/standalone-execution) for more patterns.
+
+## Error Handling
+
+```typescript
+import { QueueNotFoundError, QueueValidationError } from '@agentuity/core';
+
+const agent = createAgent('SafePublisher', {
+ handler: async (ctx, input) => {
+ try {
+ await ctx.queue.publish('notifications', input.notification);
+ return { success: true };
+ } catch (error) {
+ if (error instanceof QueueNotFoundError) {
+ ctx.logger.error('Queue does not exist', { queue: 'notifications' });
+ return { success: false, error: 'Queue not found' };
+ }
+ if (error instanceof QueueValidationError) {
+ ctx.logger.error('Invalid message', { field: error.field });
+ return { success: false, error: 'Validation failed' };
+ }
+ throw error;
+ }
+ },
+});
+```
+
+## Queue Management
+
+Create and manage queues using the CLI or `@agentuity/server` package.
+
+### CLI Commands
+
+```bash
+# Create a worker queue
+agentuity cloud queue create --name order-processing --type worker
+
+# Create a pubsub queue for broadcasting
+agentuity cloud queue create --name events --type pubsub
+
+# List all queues
+agentuity cloud queue list
+
+# Get queue details and stats
+agentuity cloud queue get order-processing
+
+# Pause/resume processing
+agentuity cloud queue pause order-processing
+agentuity cloud queue resume order-processing
+
+# Delete a queue
+agentuity cloud queue delete order-processing
+```
+
+For programmatic queue management, see [SDK Utilities for External Apps](/Learn/Cookbook/Patterns/server-utilities#queue-management).
+
+## Consuming Messages
+
+### Webhook Destinations
+
+Configure webhook destinations to automatically deliver messages to HTTP endpoints. Set these up in the [App](https://app-v1.agentuity.com) or [programmatically](/Learn/Cookbook/Patterns/server-utilities#webhook-destinations).
+
+Webhook destinations support:
+- Custom headers and authentication
+- Configurable timeouts (up to 30 seconds)
+- Retry policies with exponential backoff
+
+### Pull-Based Consumption
+
+For workers that pull and process messages, see [Pull-Based Consumption](/Learn/Cookbook/Patterns/server-utilities#pull-based-consumption). This pattern is useful for long-running workers that need fine-grained control over message processing.
+
+## Dead Letter Queue
+
+Messages that exceed the retry limit are moved to the dead letter queue (DLQ). Inspect and replay failed messages:
+
+```bash
+# List failed messages
+agentuity cloud queue dlq order-processing
+
+# Replay a message back to the queue
+agentuity cloud queue dlq replay order-processing --message-id msg_abc123
+
+# Purge all DLQ messages
+agentuity cloud queue dlq purge order-processing
+```
+
+For programmatic DLQ access, see [Dead Letter Queue Operations](/Learn/Cookbook/Patterns/server-utilities#dead-letter-queue-operations).
+
+## HTTP Ingestion Sources
+
+Create public HTTP endpoints to ingest data into queues from external services. Configure these in the [App](https://app-v1.agentuity.com) or [programmatically](/Learn/Cookbook/Patterns/server-utilities#http-ingestion-sources).
+
+| Auth Type | Description |
+|-----------|-------------|
+| `none` | No authentication |
+| `basic` | HTTP Basic Auth (`username:password`) |
+| `header` | Custom header value (`Bearer token`) |
+
+## Queue Settings
+
+Configure queue behavior when creating or updating:
+
+| Setting | Default | Description |
+|---------|---------|-------------|
+| `default_ttl_seconds` | null | Message expiration (null = never) |
+| `default_visibility_timeout_seconds` | 30 | Processing timeout before message returns to queue |
+| `default_max_retries` | 5 | Attempts before moving to DLQ |
+| `default_retry_backoff_ms` | 1000 | Initial retry delay |
+| `default_retry_max_backoff_ms` | 60000 | Maximum retry delay |
+| `default_retry_multiplier` | 2.0 | Exponential backoff multiplier |
+| `max_in_flight_per_client` | 10 | Concurrent messages per consumer |
+| `retention_seconds` | 2592000 | How long to keep acknowledged messages (30 days) |
+
+## Validation Limits
+
+| Limit | Value |
+|-------|-------|
+| Queue name length | 1-256 characters |
+| Queue name format | Lowercase letters, digits, underscores, hyphens. Must start with letter or underscore. |
+| Payload size | 1 MB max |
+| Partition key length | 256 characters max |
+| Idempotency key length | 256 characters max |
+| Batch size | 1000 messages max |
+
+## Best Practices
+
+- **Use idempotency keys** for operations that shouldn't be duplicated (payments, emails)
+- **Set appropriate TTLs** for time-sensitive messages
+- **Use partition keys** when message ordering matters within a group
+- **Monitor DLQ** regularly to catch and fix processing failures
+- **Configure webhook retry policies** to handle transient failures gracefully
+
+## Next Steps
+
+- [Durable Streams](/Services/Storage/durable-streams): Streaming storage for large exports
+- [Key-Value Storage](/Services/Storage/key-value): Fast caching and configuration
+- [Background Tasks](/Learn/Cookbook/Patterns/background-tasks): Patterns for async processing
+- [Webhook Handler](/Learn/Cookbook/Patterns/webhook-handler): Receiving external webhooks