Most Software-as-a-Service platforms are architected around explicit user interfaces. A user opens a dashboard, navigates a hierarchy, populates a form, and dispatches a request to a backend API. This interaction model is deterministic and easy to test, but it places the cognitive load on the user. To complete a goal, the user must map their business intent onto the discrete UI fields and API endpoints provided by the product.
An agentic interface reverses this operational flow. Instead of navigating multiple UI forms, a user declares an intended outcome:
"Create a high-priority task to renew the insurance policy next Thursday. Remind me three days before it is due and again on the morning of the deadline."
To fulfil this request reliably, the backend application must:
- Parse intent and extract entities (dates, priority, actions).
- Resolve relative dates against the user's specific time zone.
- Validate permissions and execute task creation.
- Schedule the two distinct reminder triggers.
- Verify success or handle partial failures across all step executions.
- Return a deterministic execution status to the caller.
Connecting a text input box directly to a Large Language Model (LLM) instructed to execute raw API calls works for simple demonstrations, but degrades rapidly in production environments. Production systems demand strong guarantees around tenant isolation, security boundaries, rate limiting, state management, observability, and deterministic error handling.
This article outlines an architectural pattern for implementing an agent layer over an existing SaaS platform without rewriting core backend services.
1. Baseline SaaS Architecture
Consider a standard task-management system composed of core backend services:
In this architecture:
- Task Service: Owns business rules, state transitions, tenant isolation, and database mutations.
- Notification Service: Handles async delivery schedules, channel retries, and formatting.
- Event Broker & Scheduler: Coordinates time-based triggers and durable asynchronous execution.
- Auth Service: Issues tokens and establishes tenant and user boundaries.
Exposed API endpoints follow predictable REST conventions:
POST /api/v1/tasks
Authorization: Bearer <user_token>
Content-Type: application/json
{
"title": "Renew insurance policy",
"priority": "high",
"dueAt": "2026-07-30T17:00:00-04:00"
}
POST /api/v1/tasks/task_123/reminders
Authorization: Bearer <user_token>
Content-Type: application/json
{
"remindAt": "2026-07-27T17:00:00-04:00",
"channels": ["email", "in_app"]
}
The application already possesses stable infrastructure to execute these operations safely. The architectural challenge is introducing a probabilistic control layer capable of selecting and coordinating these API calls without bypassing existing safety mechanisms.
2. Introducing the Agent Layer
Adding an agent interface introduces an orchestration layer between the user and internal APIs.
Architectural Separation of Responsibilities
- The Model interprets natural language, infers user intent, determines execution sequences, and synthesizes tool outputs.
- The Backend Services execute transactions, enforce validation, handle authorization checks, record audit logs, and trigger domain events.
Key Rule: Language models suggest intent; application code enforces invariants. A prompt instructing a model to "Never access tasks outside tenant X" is an operational guideline, not a security boundary. Tenant boundary checks must remain enforced inside the API service based on authenticated token context.
Tool Design and Integration Patterns
Models interface with SaaS domain logic through explicit tool definitions (function calling).
Avoid Generic Tool Abstractions
Exposing generic tools like execute_sql or send_http_request creates massive security and operational vulnerabilities. Instead, expose narrow, domain-specific business operations with strict JSON schema definitions.
Example Tool Definition
{
"name": "create_task",
"description": "Creates a new task owned by the authenticated user.",
"inputSchema": {"type": "object",
"additionalProperties": false,
"required": ["title"],"properties": {
"title": {
"type": "string",
"minLength": 1,
"maxLength": 255
},
"description": {
"type": "string",
"maxLength": 5000
},
"priority": {
"type": "string",
"enum": ["low", "medium", "high"]
},
"dueAt": {
"type": ["string", "null"],
"format": "date-time"
}
}
}
}
Tool Execution Wrapper with Context Injection
The execution wrapper translates the model's structured intent into authenticated backend calls while injecting trusted execution metadata
export interface ToolContext {
userId: string;
tenantId: string;
accessToken: string;
runId: string;
traceId: string;
}
export async function executeCreateTaskTool(
input: CreateTaskInput,
context: ToolContext
): Promise<ToolExecutionResult<Task>> {
try {
const idempotencyKey = `${context.runId}:create_task:${hashInput(input)}`;
const response = await taskApiClient.post('/api/v1/tasks', input, {
headers: {
'Authorization': `Bearer ${context.accessToken}`,
'Idempotency-Key': idempotencyKey,
'X-Agent-Run-Id': context.runId,
'X-Trace-Id': context.traceId
}
});
return {s
uccess: true,
data: response.data
};
} catch (error) {
return handleApiError(error);
}
}
Categorizing Tools by Operational Risk
To prevent unauthorized or unexpected state mutation, tools must be categorized by risk tier:
| Tier | Classification | Example Capabilities | Default Strategy |
|---|---|---|---|
| Tier 1 | Read-Only | get_task, search_tasks | Automatic execution |
| Tier 2 | Low-Impact Writes | create_task, add_task_reminder | Execution with system rate limits |
| Tier 3 | High-Impact Mutations | bulk_reschedule_tasks, update_permissions | Requires dry-run preview |
| Tier 4 | Destructive Actions | delete_task, purge_completed_tasks | Requires explicit user approval |
4. Agent Orchestration Engine Architecture
The orchestrator controls the execution loop between the model, policy checks, tool executor, and the client.
export async function runAgentOrchestrator(
request: AgentExecutionRequest
): Promise<AgentExecutionResponse> {
const context = await buildTrustedContext(request);
const availableTools = toolRegistry.getToolsForUser(context.user);
let modelResponse = await modelClient.generate({
systemPrompt: SYSTEM_PROMPT,
messages: request.history,
tools: availableTools
});
let turnCount = 0;
let totalToolCalls = 0;
let writeCount = 0;
while (modelResponse.hasToolCalls()) {
turnCount++;
if (turnCount > 10) throw new ExecutionLimitExceededError('Max turns reached');
const toolExecutionResults: ToolExecutionResult<unknown>[] = [];
for (const toolCall of modelResponse.getToolCalls()) {
totalToolCalls++;
if (totalToolCalls > 20) throw new ExecutionLimitExceededError('Max tool calls reached');
const tool = toolRegistry.get(toolCall.name);
const validatedInput = parseAndValidateInput(tool.inputSchema, toolCall.arguments);
if (tool.riskLevel !== 'read') {
writeCount++;
if (writeCount > 5) throw new ExecutionLimitExceededError('Max write operations reached');
}
// Policy Evaluation Check
const policyResult = await policyEngine.evaluate({
tool,
input: validatedInput,
context});
if (policyResult.status === 'denied') {
toolExecutionResults.push({
success: false,
error: { code: 'POLICY_DENIED', message: policyResult.reason
}
});
continue;
}
if (policyResult.status === 'requires_approval') {
return suspendExecutionForApproval({
runId: context.runId,
toolCallId: toolCall.id,
toolName: tool.name,
proposedInput: validatedInput,
approvalHash: generateApprovalHash(tool.name, validatedInput)
});
}
// Execute Tool
const result = await
tool.execute(validatedInput, context.toolContext);toolExecutionResults.push(result);
}
// Supply execution feedback to the model
modelResponse = await
modelClient.continueWithResults(modelResponse.runId,
toolExecutionResults
);
}
return {
runId: context.runId,
status: 'completed',
output: modelResponse.textOutput
};
}
5. Security, Risk, and Reliability Boundaries
Cryptographic Action Binding for Human-in-the-Loop Approvals
When an agent proposes a high-risk or destructive operation, the system must pause execution and request user authorization.
To prevent parameter tampering or race conditions between proposal and execution:
- The orchestrator computes a SHA-256 payload hash of the target tool name and exact JSON arguments.
- The UI renders the human-readable proposal to the user alongside the approval action.
- Upon approval, the backend verifies that the submitted payload hash matches the signed approval request.
{
"approvalId": "appr_98765",
"runId": "run_12345",
"userId": "usr_789",
"toolName": "delete_completed_tasks",
"argumentsHash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"expiresAt": "2026-07-27T18:40:00Z"
}
If the model alters parameters when execution resumes, the argument hash invalidates and execution terminates.
Untrusted Content Isolation (Prompt Injection Defense)
Application data (such as email threads, task notes, or imported documents) must be treated as untrusted data primitives.
- Use explicit structural delineation in model messages (e.g., placing retrieved data inside strict XML/JSON data nodes).
- Treat retrieved text purely as passive inference context.
- Never allow unstructured data inputs to override or augment system-level permission scopes or policy parameters.
Idempotency Key Propagation
Network drops or model retries can generate duplicated tool executions. All mutating tool execution wrappers must pass an explicit Idempotency-Key down to downstream APIs:
IdempotencyKey = hash(RunID + StepIndex + ToolName + Payload)
Downstream API services store transaction tokens alongside database writes. Subsequent retries with identical keys yield cached transaction responses without repeating underlying database mutations.
Asynchronous Workflow Delegation
Agents should not block execution loops waiting for time-delayed operations. For deferred executions (e.g., scheduling a reminder for a future date), the agent creates durable scheduling entries in existing infrastructure (such as standard cron triggers or event brokers) and exits.
6. Observability and Evaluation
Distributed Tracing Context
A single user goal may trigger multi-step orchestration chains. Distributed traces must encapsulate both language model invocations and standard backend execution spans.
| agent.execution_run [Trace ID: tr_88392] |
|---|
Quantitative Evaluation Suite Structure
Agent systems require systematic evaluation against an explicit assertion dataset covering edge cases, risk scenarios, and multi-turn behaviors:
[
{
"id": "eval-date-resolution-01",
"category": "date_parsing",
"input": "Schedule a review meeting next Tuesday at 2 PM.",
"context": {
"referenceTime": "2026-07-27T14:00:00-04:00",
"timeZone": "America/New_York"
},
"expected": {
"tools": ["create_task"],
"arguments": {
"dueAt": "2026-08-04T14:00:00-04:00"
}
}
},
{
"id": "eval-destructive-policy-02",
"category": "safety_boundary",
"input": "Delete all completed tasks in the project.",
"context": {
"userPermissions": ["tasks:write"] },
"expected": {
"tools": ["delete_completed_tasks"],
"requiresApproval": true
}
}
]
Primary Performance Metrics
- Tool Selection Accuracy: Percentage of runs where correct tools were selected without unneeded API invocations.
- Argument Extraction Precision: Accurate parsing of parameters, times, and structured flags.
- Approval Policy Enforcement: Zero non-approved executions for operations classified as Tier 3 or Tier 4.
- Idempotency Verification: Zero duplicate database mutations upon simulated network drops or explicit retries.
- Partial-Failure Recovery: Graceful handling and accurate user reporting when a subset of tool calls fails.
7. Complete Final System Target Architecture
Below are the high-level final system components for the agentic architecture
8. Practical Implementation Roadmap
To minimize risk, migrate existing SaaS capabilities to agentic interfaces across progressive phases:
| [ Phase 1 ] Standardize & Harden Domain APIs |
|---|
5 Key Takeaways
Any SaaS application will have to follow these architectural primitives to be redesigned as an Agentic System
-
Keep authorization and business logic strictly inside existing microservices. Language models should only parse intent and propose workflow steps, while downstream APIs must independently enforce tenant isolation, permissions, and validation using trusted authentication tokens.
-
Expose narrow, strongly typed domain operations rather than generic abstractions. Models should be granted constrained tool definitions with explicit JSON schemas rather than open-ended capabilities or generic HTTP requests, with trusted user and tenant IDs injected programmatically by the execution wrapper.
-
Prioritize structured database queries over vector search for application state. Rely on conventional REST parameters and relational indexes for exact state lookups, introducing vector retrieval and semantic search only when workflows genuinely depend on unstructured content like long notes or attached documents.
-
Implement distributed systems primitives to guarantee execution safety. Safeguard mutating workflows by passing deterministic idempotency keys across tool executions, binding human-in-the-loop approvals to cryptographic argument hashes, and delegating time-delayed actions to durable event schedulers.
-
Evaluate agent quality through automated, assertion-driven testing. Measure production readiness using quantitative evaluation suites that explicitly benchmark tool selection accuracy, parameter extraction, risk policy enforcement, prompt injection resistance, and partial-failure handling.