Class Agent
Source: source/classes/agent.prg
source\classes\agent.prg
An autonomous AI coding agent for Harbour/FiveWin. Implements the same
agentic engine that powers Agents Web:
streaming SSE chat, real tool-calling, dynamic tools, skills, and multi-agent dispatch via
hb_threadStart().
Architecture
Quick Start
oAgent := Agent():New( cApiKey )
oAgent:Run( "Crea una app TODO en PHP+SQLite con 3 sub-agentes" )
? oAgent:UsageReport()
DATA Members
| DATA | Type | Description |
|---|---|---|
aMessages | Array | Full conversation {role, content} |
aBuiltinTools | Hash | name → codeblock for all tools |
aUserTools | Hash | name → {desc, script, type} for user-created tools |
aSkills | Hash | name → content for all known skills |
aSkillsOn | Array | Names of currently active skills |
cGoal | String | Current objective |
aPlan | Array | Steps {title, state} |
cModel | String | Model ID (default: deepseek-v4-pro) |
nMaxSteps | Numeric | Max iterations per Run() (default: 14) |
nTokensIn/Out/Cache | Numeric | Token counters |
aHooks | Array | Registered hooks {name, event, block, priority, active} |
cRole | String | Agent role: "planner", "analyst", "developer", "tester" |
cRolePrompt | String | Extra system prompt injected by role |
aSharedContext | Hash | Shared blackboard between agents for inter-agent communication |
nSubAgentTimeout | Numeric | Seconds per sub-agent (default: 120) |
bOnAgentProgress | Codeblock | Callback: {|cName, cStatus, cMsg| ...} |
Core Methods
New( cKey, [cModel] ) → Agent
Constructor. Initializes tools, skills, loads saved state. Default model: deepseek-v4-pro.
Run( cPrompt ) → lastAssistantMessage
Main agent loop: observe → decide → act. Max nMaxSteps iterations (default 14).
Returns the last assistant text response. Intermediate tool calls are executed and their
results fed back into the conversation.
Step() → cResponse
Sends the current conversation to DeepSeek (POST api.deepseek.com/chat/completions),
returns the raw response. Updates token metrics. This is the core LLM RPC.
BuildSystemPrompt()
Constructs the system prompt: base instructions + active skills + user tools list + goal/plan.
BuildToolsArray() → aTools
Returns the full OpenAI-format tools array (built-in + user-registered) for the LLM request.
Built-in Tools
| Tool | Method | Description |
|---|---|---|
list_files | Tool_ListFiles([cDir]) | List files on disk |
read_file | Tool_ReadFile(cPath) | Read file content |
write_file | Tool_WriteFile(cPath,cContent) | Create/overwrite (asks permission) |
delete_file | Tool_DeleteFile(cPath) | Delete file (asks permission) |
shell | Tool_Shell(cCmd) | Execute shell command |
python | Tool_Python(cCode) | Run Python (temp .py + execute) |
sql | Tool_Sql(cDb,cQuery) | Execute SQL on a database |
web_search | Tool_WebSearch(cQuery) | Search the web |
web_fetch | Tool_WebFetch(cUrl) | Fetch a URL |
Dynamic Tools
| Method | Description |
|---|---|
RegisterTool(cName,cDesc,cScript,[cType]) | Register script as callable tool. Auto-detects python/shell type. |
UnregisterTool(cName) | Remove tool from registry. Script stays on disk. |
ListUserTools() | Return names + descriptions of user tools. |
ExecUserTool(cName,hArgs) | Execute a user-registered tool. |
Tools persist in agent_state/user_tools.json. Built-in tools are fixed; user tools
are dynamic and survive between sessions.
Hooks
Hooks are callbacks that execute before/after tool calls, enabling logging, auditing, validation, and cancellation without modifying tool code.
| Method | Description |
|---|---|
RegisterHook(cName,cEvent,bBlock,[nPriority]) | Register a hook. Events: pre_tool_use, post_tool_use, on_error, on_complete. Pre-hooks return .F. to cancel. |
UnregisterHook(cName) | Remove a hook by name. |
ListHooks() | List all registered hooks with event, priority, and status. |
RunHooks(cEvent,hContext) | Execute all hooks for an event. Called internally by ExecTool(). |
LLMRegisterHook(cName,cEvent,cAction,[nPriority]) | Register hook from LLM using preset actions: log, audit, block_shell, block_delete, block_write. |
The hook context hash hContext contains: {name, args, result}.
Pre-hooks receive the tool name and arguments; return .F. to cancel execution.
Post-hooks receive the result and can modify hContext["result"].
Skills
| Method | Description |
|---|---|
CreateSkill(cName,cContent,[cDir]) | Write skill file to skills/name.md |
ToggleSkill(cName,lOn) | Activate/deactivate without deleting the file |
ActiveSkillsPrompt() | Return prompt string with all active skills |
ListSkills([cDir]) | Load skills from disk directory |
5 built-in skills: reviewer, summarizer, refactor, documenter, tester.
Skills inject reusable instructions into the system prompt. They shape HOW the agent
thinks, not WHAT it can do (tools handle that).
Multi-Agent Orchestration
The Agent class provides a complete orchestration framework with 8 methods for coordinating multiple agents: parallel dispatch, sequential pipelines, role-based specialization, shared context, retry, timeout, aggregation, and progress monitoring.
Roles
Every Agent has a cRole and cRolePrompt property. Roles define
what the agent specializes in. The role prompt is injected into the system prompt.
oDev := Agent():New( cKey )
oDev:cRole := "developer"
oDev:cRolePrompt := "Eres desarrollador senior. Escribe código limpio y documentado."
DispatchAgents( aTasks, cContract ) → aResults
Parallel fan-out: launches up to 4 sub-agents simultaneously. Each sub-agent inherits the parent's role, shared context, and model. Results are collected synchronously.
DispatchAgentsPipeline( aTasks, cContract ) → aResults
Sequential pipeline: output of each agent feeds into the next. Use for dependent task chains where step N needs the result of step N-1.
aResults := oAgent:DispatchAgentsPipeline( { ;
"Analiza los requisitos de la app", ;
"Diseña el schema de la base de datos", ;
"Genera el código backend", ;
"Escribe los tests unitarios" }, cContract )
DispatchAgentsWithRoles( aAgentDefs, cContract ) → aResults
Fan-out with specialized roles. Each agent definition is a hash with: task, role, rolePrompt, model, nMaxSteps. Agents run in parallel with their own configuration.
aDefs := { ;
{ "task" => "Diseña el schema", "role" => "analyst", ;
"rolePrompt" => "Eres analista de datos.", "nMaxSteps" => "8" }, ;
{ "task" => "Implementa la API", "role" => "developer", ;
"model" => "gpt-4o", "nMaxSteps" => "10" }, ;
{ "task" => "Escribe tests", "role" => "tester", ;
"rolePrompt" => "Eres tester QA.", "nMaxSteps" => "6" } }
aResults := oAgent:DispatchAgentsWithRoles( aDefs, cContract )
Shared Context (Blackboard Pattern)
Agents communicate via a shared hash. Changes sync back to the parent after each sub-agent completes. The shared context is injected into every agent's system prompt.
| Method | Description |
|---|---|
GetSharedContext([cKey]) | Get entire context or a specific key. |
SetSharedContext(cKey, xValue) | Set a key in the shared context. |
oAgent:SetSharedContext( "schema", "CREATE TABLE users..." )
// sub-agents will see this in their system prompt
AggregateResults( aResults, cMethod ) → cCombined
Combine results from multiple agents. Methods:
"join"— concatenate with headers (default)"json"— encode as JSON array"summary"— LLM-powered bullet summary
RetrySubAgent( cTask, cContract, nMaxRetries ) → cResult
Retry a failed sub-agent up to nMaxRetries times (default 3). Each attempt
runs in a new agent instance with error recovery.
SubAgentRunWithTimeout( cTask, cContract, nTimeout ) → cResult
Run a sub-agent with a time limit in seconds (default 120). Uses the interrupt mechanism to abort if the agent exceeds the timeout.
NotifyProgress( cAgentName, cStatus, cMsg )
Send progress notifications. Set bOnAgentProgress codeblock to receive callbacks.
oAgent:bOnAgentProgress := {| cName, cStatus, cMsg | ;
? cName + ": " + cStatus + " — " + cMsg }
SubAgentRun( cTask, cContract ) → cResult
Individual sub-agent execution. Called by DispatchAgents() in its own thread.
Planning
GeneratePlan( cGoal ) → aPlan
Asks the LLM to break the goal into 3-6 JSON steps. Returns an array of
{title, state} where the first step is "active" and the rest "pending".
ExecutePlan()
Runs the plan sequentially: for each "active" step, calls Run() with the step
description, marks it "done", activates the next. Stops when all steps are done.
Persistence
| Method | Description |
|---|---|
SaveState(cDir) | Save user tools and active skills to user_tools.json |
LoadState(cDir) | Restore from disk on startup. Also loads skills from skills/ dir. |
Utilities
| Method | Description |
|---|---|
AddMessage(cRole,cContent) | Append to conversation array. Auto-compacts at 200 msgs. |
UsageReport() | Print token counts and estimated cost. |
Abort() | Stop the agent loop immediately. |
Complete Example
PROCEDURE Main()
LOCAL oAgent, cResult
oAgent := Agent():New( "sk-xxxx" )
// Register a custom tool
oAgent:RegisterTool( "contar", ;
"Cuenta lineas, palabras y caracteres de un archivo", ;
"contar.py" )
// Create and activate a skill
oAgent:CreateSkill( "prueba", ;
"Responde siempre en español." + CRLF + ;
"Verifica el disco antes de escribir código." + CRLF + ;
"Escribe tests automáticamente." )
oAgent:ToggleSkill( "prueba", .T. )
// Register a hook to audit all tool calls
oAgent:RegisterHook( "audit_log", "post_tool_use", ;
{| h | ? "AUDIT:", h["name"], hb_ValToStr( h["args"] ) }, 50 )
// Register a hook to block shell commands (pre-hook)
oAgent:RegisterHook( "safe_mode", "pre_tool_use", ;
{| h | iif( h["name"] == "shell", .F., .T. ) }, 10 )
// Or use LLM-driven preset hooks
oAgent:LLMRegisterHook( "llm_audit", "post_tool_use", "audit" )
// Generate and execute a plan
oAgent:GeneratePlan( "Crea una app TODO en PHP+SQLite" )
oAgent:ExecutePlan()
// Or run directly
cResult := oAgent:Run( "Explica qué hay en el disco" )
? cResult
// === Multi-agent orchestration ===
oAgent:nSubAgentTimeout := 60
// Parallel dispatch with roles
aDefs := { ;
{ "task" => "Diseña el schema", "role" => "analyst", ;
"rolePrompt" => "Eres analista de datos." }, ;
{ "task" => "Implementa la API", "role" => "developer", ;
"model" => "gpt-4o" }, ;
{ "task" => "Escribe tests", "role" => "tester" } }
aResults := oAgent:DispatchAgentsWithRoles( aDefs, cContract )
// Aggregate results
cSummary := oAgent:AggregateResults( aResults, "summary" )
? cSummary
// Pipeline: sequential dependent tasks
aResults := oAgent:DispatchAgentsPipeline( { ;
"Analiza requisitos", "Diseña schema", "Implementa API" }, cContract )
oAgent:UsageReport()
RETURN
Notes
- Harbour MT VM required for
DispatchAgents(). Link againstlibhbvmmt. - API Key: set via environment variable
DEEPSEEK_API_KEYor constructor. - Streaming: Step() uses SSE streaming with stream_options for real-time token metrics.
- Orchestration:
DispatchAgents()parallel,DispatchAgentsPipeline()sequential,DispatchAgentsWithRoles()with specialized roles. Shared context viaSetSharedContext(). Retry, timeout, aggregation included. - Roles: set
cRoleandcRolePromptto specialize each agent. Role prompt is injected into the system prompt automatically. - Hooks: pre/post callbacks on tool execution. Use
RegisterHook()for custom codeblocks, orLLMRegisterHook()with presets (log, audit, block_*). - Interrupt/Inject: pass
interruptandinjectcodeblocks to New() for external cancellation and user interjections. - Contract: the
cContractparameter inDispatchAgents()is critical. Without it, parallel sub-agents diverge on naming conventions. Always provide exact table names, column names, function signatures, and file paths. - Sample:
samples\agenticai\agenticai2.prg— a full FiveWin GUI app using the Agent class with TWebView2 chat panel.