Documentation Index
Fetch the complete documentation index at: https://grantmaster.dev/llms.txt
Use this file to discover all available pages before exploring further.
Agent Execution Architecture
| Status | Updated | Covered Files |
|---|
| 🟢 Stable | 2026-02-21 | features/agents/types.ts, AgentExecutionService.ts, AgentToolRegistry.ts, agentDefinitions.ts, AgentQuotaService.ts |
Overview
The Agent Execution Architecture enables autonomous, multi-step AI operations that combine existing services (Gemini AI, RAG, compliance) under a credit-based quota system. Agents inherit the triggering user’s RBAC permissions, enforce step/credit budgets per run, and support human-in-the-loop escalation.
User triggers agent run
│
▼
┌──────────────────────┐
│ AgentExecutionService │ Orchestrates the entire lifecycle
│ ├─ startRun() │ Validates → reserves credits → creates run doc
│ ├─ executeStep() │ Invokes tools → consumes credits
│ ├─ pauseRun() │ Creates escalation (human-in-the-loop)
│ ├─ resumeRun() │ Resolves escalation → continues
│ ├─ completeRun() │ Releases unused credits
│ └─ failRun() │ Releases all reserved credits
└──────────────────────┘
│
├──▶ AgentToolRegistry (tool lookup & permission check)
├──▶ CreditService (reserve / consume / release)
├──▶ AgentQuotaService (tier-based limits)
└──▶ EventBus (lifecycle events)
Key Components
AgentExecutionService
File: src/features/agents/services/AgentExecutionService.ts
The central orchestrator. Each method is a Firestore transaction-backed state transition on the agent run document stored at organizations/{orgId}/agent_runs/{runId}.
| Method | State Transition | Actions |
|---|
startRun() | → queued → running | Validates definition, checks permissions, checks quota (concurrent + monthly limits), reserves credits, creates Firestore doc, emits AGENT_TASK_STARTED |
executeStep() | running → running | Validates step budget, looks up tool in registry, invokes tool, consumes credits from reservation, emits AGENT_STEP_COMPLETED |
pauseRun() | running → awaiting_human | Creates AgentEscalation record, emits AGENT_ESCALATION_REQUIRED |
resumeRun() | awaiting_human → running | Records escalation resolution, emits AGENT_ESCALATION_RESOLVED |
completeRun() | running → completed | Releases unused credits, computes final token/duration stats, emits AGENT_TASK_COMPLETED |
failRun() | running → failed | Releases all reserved credits, records error, emits AGENT_TASK_FAILED |
cancelRun() | any active → cancelled | Releases all reserved credits, emits AGENT_TASK_CANCELLED |
File: src/features/agents/services/AgentToolRegistry.ts
A singleton registry mapping tool names to AgentTool implementations. Tools wrap existing service methods without modifying the original services.
// Register a tool
agentToolRegistry.register(createAgentTool({
name: 'generate_journal',
description: '...',
requiredPermissions: [Permission.USE_AI_WIZARD, Permission.EDIT_OWN_ENTRIES],
execute: async (input, context) => { /* calls geminiService */ },
}));
// Lookup at execution time
const tool = agentToolRegistry.getTool('generate_journal');
const result = await tool.execute(input, context);
Built-in tools (registered at module initialization):
| Tool | Credits | Wraps | Required Permissions |
|---|
generate_journal | 5 | geminiService.generateJournalEntries() | USE_AI_WIZARD, EDIT_OWN_ENTRIES |
analyze_compliance | 8 | geminiService.analyzeCompliance() | VIEW_JOURNALS, VIEW_PROJECTS |
forecast_budget | 10 | geminiService.generateProjectForecast() | VIEW_PROJECTS, VIEW_PROJECT_ANALYTICS |
scan_expense | 3 | geminiService.analyzeReceipt() | VIEW_EXPENSES |
generate_report | 15 | geminiService.generateReportNarrative() | GENERATE_REPORTS |
query_documents | 2 | ragService.queryRAG() | VIEW_PROJECTS |
Each tool wrapper performs permission verification before execution and returns a standardized AgentToolResult with success, output, creditsUsed, and optional error.
AgentQuotaService
File: src/features/agents/services/AgentQuotaService.ts
Enforces tier-based limits before agent runs start:
- Monthly run limit:
maxAgentRunsPerMonth from TierLimits
- Concurrent agent limit:
maxConcurrentAgents — counts runs with status running or awaiting_human
- Hourly rate limit:
agentRunsPerHour
- Per-run step budget:
maxAgentStepsPerRun
- Per-run token budget:
maxAgentTokenBudgetPerRun
Agent Definitions
File: src/features/agents/services/agentDefinitions.ts
Static configuration for each agent type. The AGENT_DEFINITIONS array is looked up by ID during startRun() to validate the request and enforce budgets.
| Agent | ID | Feature Gate | Max Steps | Credit Budget | Category |
|---|
| Compliance Checker | compliance_checker | AGENT_MULTI_STEP | 10 | 30 | compliance |
| Report Generator | report_generator | AGENT_MULTI_STEP | 15 | 50 | reporting |
| Grant Proposal Writer | grant_proposal_writer | AGENT_AUTONOMOUS | 20 | 80 | grants |
| Expense Auditor | expense_auditor | AGENT_MULTI_STEP | 15 | 40 | finance |
| Journal Assistant | journal_assistant | AGENT_BASIC | 5 | 15 | journals |
Run Lifecycle Flow
startRun()
│
┌─────────────▼─────────────┐
│ queued │
└─────────────┬─────────────┘
│ (validation passes)
┌─────────────▼─────────────┐
┌───▶│ running │◀──────────┐
│ └──┬──────────┬──────────┬──┘ │
│ │ │ │ │
│ executeStep() pauseRun() failRun() resumeRun()
│ │ │ │ │
│ ▼ ▼ ▼ │
│ (next step) awaiting failed │
│ │ _human │ │
│ │ │ │ │
└───────┘ └──────────────────────────┘
│
completeRun()
│
▼
completed
Credit Integration
Every agent run is backed by a credit reservation created at startRun() time:
- Reserve:
creditService.reserveCredits(orgId, budget, runId) — atomically checks available balance and creates a reservation
- Consume: After each
executeStep(), creditService.consumeCredits(orgId, reservationId, actualCredits) deducts from the reservation
- Release: On
completeRun(), cancelRun(), or failRun(), creditService.releaseCredits() returns uncommitted credits to the pool
This ensures concurrent agent runs cannot overdraw the organization’s credit balance.
Event Emissions
The execution service emits these events at each lifecycle transition:
| Event | When | Key Payload Fields |
|---|
AGENT_TASK_STARTED | Run successfully queued | agentType, agentRunId, triggeredBy, creditBudget |
AGENT_STEP_COMPLETED | Step finishes | agentRunId, stepIndex, toolName, creditsUsed, durationMs |
AGENT_ESCALATION_REQUIRED | Run paused for human input | agentRunId, reason, options |
AGENT_ESCALATION_RESOLVED | Human responds to escalation | agentRunId, resolvedBy, resolution |
AGENT_TASK_COMPLETED | Run finishes successfully | agentRunId, totalCreditsUsed, totalSteps, durationMs |
AGENT_TASK_FAILED | Run fails with error | agentRunId, error, failedAtStep |
AGENT_TASK_CANCELLED | Run manually cancelled | agentRunId, cancelledBy |
AGENT_BUDGET_CONSUMED | Credit budget ≥80% consumed | agentRunId, percentageUsed, creditsRemaining |
Security Model
- Agents inherit the triggering user’s RBAC permissions — captured at run start in
AgentRun.permissions
- Each tool checks permissions before execution via
createAgentTool() wrapper
- The
AgentDefinition.requiredPermissions array is validated at startRun() time
- Feature entitlement gates (
AGENT_BASIC, AGENT_MULTI_STEP, AGENT_AUTONOMOUS) are checked via the entitlement system
Firestore Data Model
organizations/{orgId}/
agent_runs/{runId} ← AgentRun document
steps: AgentStep[] ← Embedded array (not subcollection)
escalation?: AgentEscalation ← Embedded object
creditReservations/{id} ← Managed by CreditService
Adding a New Agent
- Define the agent in
agentDefinitions.ts:
const MY_AGENT: AgentDefinition = {
id: 'my_agent',
name: 'My Agent',
description: '...',
requiredFeature: Feature.AGENT_MULTI_STEP,
requiredPermissions: [Permission.VIEW_PROJECTS],
allowedTools: ['query_documents', 'generate_report'],
maxSteps: 10,
defaultCreditBudget: 25,
icon: 'Sparkles',
category: 'general',
};
- Add to
AGENT_DEFINITIONS array
- Register any new tools in
AgentToolRegistry.registerBuiltInTools()
- Add to
registerBuiltInTools() in AgentToolRegistry.ts:
registry.register(createAgentTool({
name: 'my_tool',
description: '...',
requiredPermissions: [Permission.SOME_PERM],
execute: async (input, context) => {
const { myService } = await import('@/features/my/services/myService');
const result = await myService.doSomething(input.data as string);
return { success: true, output: { result }, creditsUsed: 5 };
},
}));
- Add the tool name to the
allowedTools array of relevant agent definitions
Maintenance
Update this document when:
- Adding new agent definitions or tools
- Changing the credit reservation/consumption flow
- Modifying the run state machine
- Adding new event emissions