๐Ÿค–

05-Agent-Providers

๐Ÿค–

Two layers: low-level providers (OpenAI, Claude API, Claude Code, local shell) and high-level specialized agents (Architect, Backend, Frontend, iPhone, RAG, QA, DevOps, Documentation).

1. Provider interface

interface AgentProviderInterface {
	public function key(): string;                         // 'openai' | 'claude_api' | 'claude_code' | 'local_shell'
	public function capabilities(): array;                 // ['chat','tools','streaming','code_exec']

	public function prepareContext(AgentRun $run, RagResult $rag): ProviderContext;
	public function startRun(ProviderContext $ctx): ProviderStream;
	public function continueRun(AgentRun $run, string $userInput): ProviderStream;
	public function pauseRun(AgentRun $run): void;
	public function cancelRun(AgentRun $run): void;
	public function summarizeRun(AgentRun $run): RunSummary;
	public function extractFileChanges(ProviderEvent $e): array;   // FileChange[]
	public function streamEvents(ProviderStream $s): iterable;     // yields ProviderEvent
}

ProviderEvent is normalized:

final class ProviderEvent {
	public string $kind;          // 'message','tool_call','tool_result','file_change','command','done','error','pause'
	public array $payload;
	public ?string $rawProviderType = null;
}

The orchestrator maps ProviderEvent โ†’ agent_events rows via ConsoleEventService.

2. Provider implementations

ServiceNotes
OpenAIAgentServiceUses Responses API with tools; streams via SSE; converts tool calls โ†’ orchestrator-mediated tool executions.
ClaudeAgentServiceAnthropic Messages API with tool use; same normalization.
ClaudeCodeAgentServiceWraps the Claude Code CLI/SDK. Sends workspace path, allowed tools, structured stop signals.
LocalShellRunnerInternal-only provider used by the orchestrator for tool execution; not chosen by users.

All provider classes are constructor-injected and bound to the container in AgentProvidersServiceProvider. The AgentProviderResolver returns the right instance from selected_model / selected_agent.

3. Orchestrator

AgentRunOrchestrator is the single state machine. Responsibilities:

  1. acquire workspace lock
  1. snapshot pre_run
  1. create Git branch agent/run-{id}-{slug}
  1. retrieve RAG context (unless disabled)
  1. call provider->prepareContext() and startRun()
  1. iterate normalized events:
    • tool calls โ†’ CommandExecutionService / WorkspaceService / GitService
    • file changes โ†’ WorkspaceService::applyChange() + WorkspaceFile row + diff event
    • pause โ†’ set status waiting_for_user, persist, broadcast
  1. on completion โ†’ run DocumentationAutoUpdateService, write final_summary, release lock
  1. on failure โ†’ write failed_reason, still release lock, emit final_summary event with severity: error

State transitions match the diagram in the master page (ยง4).

4. Tool surface exposed to providers

ToolBacked byNotes
read_file(path)WorkspaceServicePath is normalized & sandboxed.
list_files(glob?)WorkspaceServiceReturns relative paths only.
apply_patch(unified_diff)WorkspaceServicePre-validates against working tree; emits file_* events.
run_command(cmd, args, timeout?)CommandExecutionServiceAllowlist-gated; destructive โ†’ auto pre_command snapshot.
rag_search(query, opts?)RagContextServiceReturns chunks + debug.
git(op, args?)GitServiceConstrained to status/diff/branch/commit/restore.
ask_user(question, options?)OrchestratorTriggers paused_for_input.
finish(summary)OrchestratorTriggers final_summary โ€ข run completion.

Tools are advertised to each provider in its native schema. Provider-specific shaping lives in the provider class.

5. Specialized agents

Each specialized agent is a configuration object:

final class AgentDefinition {
	public string $key;                     // 'architect', 'backend', ...
	public string $displayName;
	public string $systemPrompt;            // role-specific
	public array $allowedTools;             // subset of the tool surface
	public array $defaultModel;             // ['provider' => 'openai', 'model' => 'gpt-4.1']
	public array $ragFilters;               // default source_types to bias retrieval
}

Roles

AgentBiasAllowed tools (extra)
Architectreads structure, writes plans, never edits prod code without explicit askread_file, list_files, rag_search, ask_user, finish
Backendcontrollers, models, migrations, services, routes, APIs, testsall
FrontendBlade / Livewire / Vue / React, UI, formsall except destructive Git
iPhoneSwiftUI screens, API client, auth flowread_file, list_files, apply_patch, rag_search, finish
RAGindexes, embeddings, stale detectionread_file, list_files, run_command (index), rag_search, finish
QAtests, route checks, migration checks, API responses, UI flowsrun_command (test), read_file, list_files, finish
DevOpsserver params, queues, Redis, Git, deploysrun_command (whitelisted ops), git, finish
Documentationupdates docs/changelogs/diagrams/rulesread_file, apply_patch (docs only), rag_search, finish

All agents share the same RAG, same event log, same workspace.

6. Prompt skeleton (shared system prompt header)

You are the {agent_display_name} for the AI Coding Agent Workspace.
Project: {project.name} ({framework}/{language}).
Branch: {run.branch_name} (an isolated branch for this run).
Safe mode: {safe_mode_on_off}.
RAG: {rag_on_off}. If ON, you must call rag_search before any plan or edit.
Never guess when context exists. If unsure, call ask_user.
Never push, never delete history, never run blocked commands.
Always finish() with a structured summary describing changes, risks, and tests.

Role-specific suffix appended per agent.

7. Failure handling

  • Provider error โ†’ emit error event, transition to failed, do not auto-rollback (leave the user to choose).
  • Tool error โ†’ propagate to provider as a tool_result with is_error: true; agent decides recovery.
  • Timeout โ†’ kill subprocess, mark was_killed=true on the command row, transition to failed unless agent recovers gracefully.

8. Pause / resume

  • paused_for_input event carries { question, options?, deadline? }.
  • Orchestrator persists provider conversation state in agent_runs.metadata_json.provider_state.
  • POST /api/runs/{run}/resume with { input: "โ€ฆ" } rehydrates and calls provider->continueRun().