Recipe: Production SaaS Agent
SkillMonitoring & opsFull recipe for a production-ready SaaS agent with guardrails, per-user cost isolation, rate limiting, A2A exposure, audit logging, and graceful error handling.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Recipe: Production SaaS Agent skill
What this skill tells your AI
The instructions your AI receives, as published by tylerjrbuell/reactive-agents-ts in apps/docs/skills/recipe-saas-agent/SKILL.md and read by ahel’s review.
What this builds
A production-ready agent designed for multi-tenant SaaS deployment. Includes prompt injection protection, per-user cost tracking, behavioral contracts, rate limiting, circuit breakers, A2A networking, and full audit logging.
Skills loaded by this recipe
identity-and-guardrails— injection detection, PII masking, behavioral contractscost-budget-enforcement— per-request and daily cost limits, rate limitingobservability-instrumentation— audit logging, minimal verbositya2a-agent-networking— A2A server exposure for service meshreasoning-strategy-selection— adaptive strategy
Complete implementation
import { ReactiveAgents } from "@reactive-agents/runtime";
export async function createSaaSAgent(userId: string, plan: "free" | "pro" | "enterprise") {
const budgets = {
free: { perSession: 0.10, daily: 0.50 },
pro: { perSession: 0.50, daily: 5.00 },
enterprise: { perSession: 2.00, daily: 50.00 },
}[plan];
return ReactiveAgents.create()
.withName(`saas-agent-${userId}`)
.withProvider("anthropic")
.withReasoning({
defaultStrategy: "adaptive",
maxIterations: 15,
})
.withTools({
allowedTools: ["web-search", "http-get", "checkpoint", "final-answer"],
})
.withGuardrails({
injection: true, // block prompt injection attacks
pii: true, // mask PII in inputs and outputs
toxicity: true,
})
.withBehavioralContracts({
deniedTools: ["file-write", "shell-execute", "code-execute"],
maxToolCalls: 30,
maxIterations: 15,
maxOutputLength: 10_000,
requireDisclosure: true,
})
.withCostTracking(budgets)
.withRateLimiting({
requestsPerMinute: plan === "free" ? 10 : 60,
tokensPerMinute: plan === "free" ? 10_000 : 100_000,
maxConcurrent: plan === "free" ? 1 : 5,
})
.withCircuitBreaker({
failureThreshold: 5,
windowMs: 60_000,
retryAfterMs: 30_000,
})
.withAudit()
.withObservability({
verbosity: "minimal", // silent in production — results returned programmatically
file: `./logs/users/${userId}.jsonl`,
})
.withA2A({ port: 8000 }) // expose for internal service mesh
.build();
}
// Usage per request:
app.post("/api/chat", async (req, res) => {
const { userId, plan, prompt } = req.body;
const agent = await createSaaSAgent(userId, plan);
try {
const result = await agent.run(prompt);
res.json({ output: result.output, cost: result.cost });
} catch (error) {
if (error.name === "BudgetExceededError") {
res.status(429).json({ error: "Usage limit reached. Upgrade your plan." });
} else if (error.name === "GuardrailViolation") {
res.status(400).json({ error: "Request blocked by safety filter." });
} else {
res.status(500).json({ error: "Agent error." });
}
} finally {
await agent.dispose();
}
});
Per-user agent pattern
For SaaS, build one agent per request (not shared) to ensure isolation:
// ✅ Per-request — isolated context, separate cost tracking
app.post("/api/chat", async (req, res) => {
const agent = await createSaaSAgent(req.body.userId, req.body.plan);
const result = await agent.run(req.body.prompt);
await agent.dispose();
res.json({ output: result.output });
});
// ❌ Shared agent — users could see each other's conversation context
const sharedAgent = await createSaaSAgent("shared", "pro");
app.post("/api/chat", async (req, res) => {
const result = await sharedAgent.run(req.body.prompt); // UNSAFE
res.json({ output: result.output });
});
Streaming for SaaS
import { AgentStream } from "@reactive-agents/runtime";
app.post("/api/stream", async (req, res) => {
const { userId, plan, prompt } = req.body;
const agent = await createSaaSAgent(userId, plan);
// SSE streaming response
res.setHeader("Content-Type", "text/event-stream");
try {
const stream = agent.runStream(prompt);
for await (const event of stream) {
if (event.type === "TextDelta") {
res.write(`data: ${JSON.stringify({ text: event.content })}\n\n`);
}
if (event.type === "StreamCompleted") break;
}
} finally {
res.end();
await agent.dispose();
}
});
Error types to handle
| Error | Cause | Response |
|---|---|---|
BudgetExceededError | Cost or token limit hit | 429 — prompt to upgrade |
GuardrailViolation | Injection / PII / toxicity detected | 400 — blocked by safety |
ContractViolation | Behavioral contract exceeded | 400 — usage policy exceeded |
CircuitBreakerOpenError | Provider failures | 503 — retry after delay |
RateLimitError | Requests/tokens per minute exceeded | 429 — retry after delay |
Pitfalls
- Build agents per-request, not at server startup — module-level agents share context across users
.withA2A({ port: 8000 })is for internal service mesh use — do not expose the A2A port publicly without authenticationverbosity: "minimal"suppresses all console output — ensurefilelogging is configured, or errors will be invisiblerequireDisclosure: truerequires the agent to state it is an AI in its first response — test this in your system promptpii: truemasks PII before it reaches the LLM — if your agent legitimately processes PII (e.g., user profile management), disable this detector selectively
Signals
- GitHub stars
- 27
- Forks
- 4
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
recipe-saas-agent- Source
- github.com/tylerjrbuell/reactive-agents-ts