18. Agent Orchestration
MALDA: The AI-First Programming Language includes native, first-class support for agent orchestration as a core language feature. These capabilities are built directly into the language, making MALDA the simplest way to build AI agents and automation workflows.
18.1 ⚠️ Critical Security and Risk Disclaimer
Autonomous agent systems with tool access pose significant security, safety, and operational risks. Before deploying agents with tools in production environments, you must understand and mitigate these risks.
Fundamental Risks
- Unintended Actions: Agents may execute tools in ways you did not anticipate, leading to data loss, system corruption, or security breaches.
- Tool Misuse: Agents can combine tools in unexpected ways, potentially bypassing intended safeguards or restrictions.
- LLM Hallucination: Language models may misinterpret instructions, generate incorrect tool parameters, or make decisions based on flawed reasoning.
- Autonomous Execution: Once started, agents may perform many actions without human oversight, amplifying the impact of any single error.
- Context Limitations: Agents operate with limited context and may not understand the full implications of their actions.
- Prompt Injection: Malicious or malformed input can cause agents to behave in unintended ways, potentially executing dangerous operations.
Real-World Risk Scenarios
1. Data Loss and File System Damage
- Scenario: An agent tasked with "cleaning up old files" misinterprets the scope and deletes critical system files, configuration files, or entire project directories.
- Example: Agent receives instruction to "remove temporary files" but deletes
node_modules,.git, or source code files, causing irreversible data loss. - Impact: Project corruption, loss of work, system instability, inability to recover without backups.
2. Unauthorized Code Execution
- Scenario: An agent with
run_commandtool access executes malicious or destructive shell commands. - Example: Agent interprets a user request to "optimize the system" and runs
rm -rf /(or equivalent),format C:, or other destructive commands. - Impact: Complete system destruction, data loss, security compromise, unauthorized access to other systems.
3. Git Repository Corruption
- Scenario: A GitAgent with push permissions accidentally force-pushes to main/master, overwriting production code or deleting branches.
- Example: Agent attempts to "fix merge conflicts" by force-pushing, destroying commit history and causing team-wide disruption.
- Impact: Loss of version history, broken deployments, team productivity loss, potential data loss if backups are insufficient.
4. Unauthorized Access and Information Disclosure
- Scenario: An agent with file read access reads sensitive files (API keys, passwords, personal data) and includes them in responses or logs.
- Example: Agent reads
.envfiles, configuration files containing secrets, or private user data and exposes them in conversation history or error messages. - Impact: Security breach, privacy violations, regulatory compliance issues, financial liability.
5. Infinite Loops and Resource Exhaustion
- Scenario: An agent enters an infinite loop of tool calls, consuming system resources indefinitely.
- Example: Agent repeatedly reads and writes the same file, creates recursive directory structures, or makes endless API calls, exhausting CPU, memory, or API quotas.
- Impact: System crashes, service unavailability, unexpected costs (API usage), denial of service.
6. Cascading Failures in Multi-Agent Systems
- Scenario: One agent's error triggers a chain reaction of failures across multiple agents in a hierarchical system.
- Example: An orchestrator agent delegates a task that causes a subagent to corrupt files, which then causes other agents to fail, leading to widespread system damage.
- Impact: Amplified damage, difficult-to-trace root causes, complex recovery scenarios.
7. Production System Modification
- Scenario: An agent intended for development environments is accidentally run against production systems.
- Example: Agent modifies production database configurations, changes live API endpoints, or updates critical infrastructure code without proper testing.
- Impact: Service outages, data corruption, customer impact, financial losses, reputation damage.
8. Malicious Code Injection
- Scenario: An agent writes code that includes malicious payloads, backdoors, or security vulnerabilities.
- Example: Agent generates code that includes hardcoded credentials, executes arbitrary commands from user input, or introduces SQL injection vulnerabilities.
- Impact: Security vulnerabilities, unauthorized access, data breaches, compliance violations.
Mitigation Strategies
- Sandboxing: Always run agents in isolated environments with restricted file system access and network permissions.
- Working Directory Restrictions: Use working directory parameters to limit file operations to specific, non-critical directories.
- Human-in-the-Loop: Require explicit approval for destructive operations (deletes, git pushes, command execution).
- Tool Filtering: Only provide agents with the minimum set of tools necessary for their specific task.
- Input Validation: Validate and sanitize all user inputs before passing them to agents.
- Rate Limiting: Implement limits on tool call frequency and total operations per agent session.
- Monitoring and Logging: Log all tool calls and agent decisions for audit trails and debugging.
- Backup and Recovery: Maintain regular backups and test recovery procedures before deploying agents.
- Testing: Thoroughly test agents in safe, isolated environments before production use.
- Principle of Least Privilege: Grant agents only the minimum permissions required for their tasks.
- Timeouts: Set maximum execution times to prevent infinite loops and resource exhaustion.
- Code Review: Review all code generated by agents before deployment, especially for security-sensitive operations.
Best Practices
- Start Small: Begin with read-only operations and gradually add write capabilities only after thorough testing.
- Use Specialized Agents: Prefer specialized agents (CodingAgent, GitAgent) with built-in safeguards over generic Agent classes with unrestricted tools.
- Explicit Instructions: Provide clear, specific instructions to agents and avoid ambiguous or overly broad directives.
- Version Control: Always use version control and never allow agents to force-push or modify protected branches without explicit approval.
- Environment Separation: Maintain strict separation between development, staging, and production environments.
- Regular Audits: Periodically review agent behavior, tool usage patterns, and system changes.
By using MALDA's agent orchestration features, you acknowledge that you understand these risks and are solely responsible for the actions taken by agents you create and deploy. The developers of MALDA and this reference manual provide no warranties regarding the safety, security, or reliability of autonomous agent systems.
18.2 LLM Client
The LLMClient class provides an interface to OpenAI-compatible LLM APIs (OpenAI, OpenRouter, LMStudio, OLLAMA, etc.).
Constructor
var client = new LLMClient(apiUrl, apiKey, model);
Methods
chat(messages, tools)→object: Send conversation to LLM with optional tool definitionscomplete(prompt)→string: Send single prompt and get completionsetTemperature(temp): Set temperature parameter (0.0 to 2.0)setMaxTokens(tokens): Set maximum tokens in response
Example
var apiKey = getEnv("OPENAI_API_KEY");
var client = new LLMClient(
"https://api.openai.com/v1/chat/completions",
apiKey,
"gpt-3.5-turbo"
);
var response = client.complete("Explain quantum computing in one sentence.");
print(response);
18.3 OpenRouterClient
For OpenRouter specifically, there's a specialized OpenRouterClient class that simplifies configuration.
Constructor
// With default model (deepseek/deepseek-v4-flash)
var client = new OpenRouterClient();
// With custom model
var client = new OpenRouterClient("openai/gpt-4");
OPENROUTER_API_KEY environment variable and uses the correct OpenRouter endpoint.
App attribution (OpenRouter analytics)
Set these properties so OpenRouter can attribute token usage to each of your apps
(App Attribution). Use a distinct
httpReferer URL per application. Analytics appear at
https://openrouter.ai/apps?url=<your-url>.
var client = new OpenRouterClient();
client.httpReferer = "https://example.com/secondbrain"; // HTTP-Referer (required for attribution)
client.appTitle = "Second Brain ASK"; // X-OpenRouter-Title
client.appCategories = "cli-agent"; // optional X-OpenRouter-Categories
18.3.1 Default local LLM (when no client is provided)
When you create an agent, prompt, or use built-ins like decomposeTask without passing an LLM client, MALDA uses a default local LLM: Qwen/Qwen2.5-0.5B-Instruct, downloaded as the GGUF build qwen2.5-0.5b-instruct-q4_k_m.gguf from Hugging Face on first use. The model is cached under your user profile (e.g. %LOCALAPPDATA%\MaldaLang\Models\default on Windows). No API key or network is required after the first download. To use a remote model instead, pass an OpenRouterClient, LLMClient, or LlamaCppClient explicitly.
You can override the default model with the environment variable MALDA_DEFAULT_LOCAL_MODEL. Set it to modelId/fileName.gguf (e.g. org/repo/MyModel-Q4_K_M.gguf) to use a different Hugging Face GGUF model; that model will be downloaded and cached in a subfolder named after the model ID. If unset, the built-in default Qwen/Qwen2.5-0.5B-Instruct GGUF build is used.
18.4 LlamaCppClient
The LlamaCppClient class enables local LLM inference using LLAMA.cpp with GGUF format models. No API keys or network connection required. If you omit modelPath, MALDA uses the default local Qwen/Qwen2.5-0.5B-Instruct GGUF model and auto-downloads it on first use.
Constructor
var client = new LlamaCppClient(modelPath); // Custom GGUF file
var client = new LlamaCppClient(); // Default auto-downloaded local model
Example
var client = new LlamaCppClient();
client.setTemperature(0.7);
client.setMaxTokens(2000);
client.setGpuLayerCount(35); // Offload layers to GPU (optional)
var conversation = new Conversation(client, "You are a helpful assistant.");
conversation.addUserMessage("Explain quantum computing.");
var response = conversation.send();
print(response.content);
18.5 Conversation Management
The Conversation class manages multi-turn conversations with LLMs, including automatic tool call handling.
Constructor
var conversation = new Conversation(client, systemPrompt);
Methods
addUserMessage(content): Add a user messageaddAssistantMessage(content): Add an assistant messageaddTool(tool): Add a tool definition for function callingsend()→object: Send conversation and get response (handles tool calls automatically)getMessages()→array: Get all messagesclear(): Clear conversation history
Example
var conversation = new Conversation(client, "You are a helpful assistant.");
conversation.addUserMessage("What is 2+2?");
var response = conversation.send();
print(response.content);
Parallel read-only tool calls
When the LLM returns multiple read-only tool calls in a single response, Conversation executes them in parallel by default. This applies to all agents (Agent, DevAgent, Ralph Wiggum loops, IDE chat) because they share the same runtime.
Parallel-safe built-in tools: read_file, grep, list_directory, get_symbols, get_parse_errors, web_search, recall_progress, and read-only git tools (git_status, git_log, git_diff, git_branch).
Always sequential: file writes, git mutations, run_command, ask_user, remember_progress, MCP tools, agent-as-tool wrappers, and custom @Tool handlers. A mutating tool in the middle of a batch splits execution so ordering stays safe.
Tool result messages are appended in the same order as the original tool_calls array before the next LLM request.
Configuration: parallel execution is on when MALDA_PARALLEL_TOOL_CALLS is unset. Set MALDA_PARALLEL_TOOL_CALLS=false (or 0, off) to disable. With verbose logging (MALDA_RALPH_VERBOSE or MALDA_AGENT_VERBOSE), parallel batches are logged explicitly.
Verbose logging, thinking feedback, and LLM streaming
During agent.think() (and any code path using Conversation.send()), the runtime can print live progress to the console:
[llm] round N— each LLM request/response round (tool-call summary or final response size)[think]— model-native reasoning or planning text before tool calls[tool]— tool executions (compact by default)
HTTP LLM clients (LLMClient, OpenRouterClient) use OpenAI-style SSE streaming (stream: true) by default. When verbose logging and thinking output are enabled, [think] text is written token-by-token as deltas arrive. Tool-call JSON is accumulated silently during the stream; tool names are logged after the stream completes.
Not streamed today: LlamaCppClient, bridge backends, and requests with structured response_format (those fall back to a single blocking response).
| Variable | Default | Purpose |
|---|---|---|
MALDA_AGENT_VERBOSE | false (Ralph: true) | Enable [llm] / [tool] logging |
MALDA_RALPH_VERBOSE | — | Alias for MALDA_AGENT_VERBOSE |
MALDA_AGENT_RICH | true | Colored Spectre.Console output (auto-off when redirected) |
MALDA_AGENT_TOOL_DETAIL | compact | compact or full tool logs |
MALDA_AGENT_LLM_THINKING | compact | [think] output: off, compact, or full |
MALDA_AGENT_LLM_STREAM | true | OpenAI SSE streaming for HTTP clients; live [think] when thinking is enabled |
MALDA_AGENT_LLM_PREVIEW | compact | Final-response preview when streaming/thinking is off |
MALDA_AGENT_STATUS_EVERY | 4 | Repeat status banner every N LLM rounds (Ralph) |
MALDA_RALPH_* | — | Aliases for the agent settings above in Ralph runs |
The same diagnostics can be toggled from code. These helpers change presentation only and never change an agent's result:
enableAgentVerboseLogging(enabled?)— turns verbose conversation logging on or off. Called with no argument it enables logging and returnstrue.setAgentVerbosePhase(phase)— labels the phase shown alongside verbose output, for example"planning"or"review".setAgentStatusBanner(banner)— sets the banner text displayed above agent progress output.
enableAgentVerboseLogging();
setAgentStatusBanner("Nightly refactor run");
setAgentVerbosePhase("planning");
18.6 Tool System
Tools allow LLMs to call functions during conversations. Tools are defined using the Tool class and follow the OpenAI function calling format.
Built-in Tool Creators
createReadFileTool(workingDirectory?)→ToolcreateWriteFileTool(workingDirectory?)→ToolcreateReplaceInFileTool(workingDirectory?)→Tool- Single replacementcreateEditFileTool(workingDirectory?)→Tool- Multiple replacementscreateInsertAtLineTool(workingDirectory?)→ToolcreateGrepTool(workingDirectory?)→ToolcreateListDirectoryTool(workingDirectory?)→ToolcreateAskUserTool()→ToolcreateGitStatusTool(workingDirectory?)→ToolcreateGitAddTool(workingDirectory?)→ToolcreateGitCommitTool(workingDirectory?)→ToolcreateGitLogTool(workingDirectory?)→ToolcreateGitDiffTool(workingDirectory?)→ToolcreateGitBranchTool(workingDirectory?)→ToolcreateGitCheckoutTool(workingDirectory?)→ToolcreateGitPushTool(workingDirectory?)→ToolcreateGitPullTool(workingDirectory?)→ToolcreateRunCommandTool(workingDirectory?)→Tool- Execute shell commands (e.g.,dotnet,npm,python) with safety checks. Parameters:command(string),args(array of strings, optional),workingDirectory(optional),timeoutMs(optional). Returns object withexitCode,stdout, andstderr.createRunMALDATool(workingDirectory?)→Tool- Execute MALDA code from a file path or source string. Parameters:sourceOrFilePath(string),input(optional string for stdin). Returns object withsuccess(boolean),output(string),error(string for parse errors), and optionalruntimeError(string).createCompileMALDATool(workingDirectory?)→Tool- Compile MALDA source code to executable. Parameters:sourcePath(string),outputPath(optional string),mode(optional:"interpreter"|"transpile"). Returns object withsuccess(boolean),outputPath(string or null),error(string), anderrors(array of error objects).createGetSymbolsTool(workingDirectory?)→Tool- Extract classes, functions, actors, and prompts from a filecreateGetParseErrorsTool(workingDirectory?)→Tool- Validate syntax without running the filecreateWebSearchTool()→Tool- Brave Search API (no working directory)createGlobTool(workingDirectory?)→Tool- Pattern-based path search. Interpreter-only; not available in transpiled executablescreateCreateMcpAgentScriptTool(workingDirectory?)→Tool- Generates MCP agent scriptscreateSubmitPlanTool()→Tool- Submit a structured plan (steps withid,description, optionaldependsOn). Parameters:plan(object withsteps) orsteps(array), optionaltaskSummary. Returns{ accepted: true, planId, stepCount }or{ accepted: false, error }. No working directory.
All of these factories accept an optional workingDirectory except createSubmitPlanTool, createAskUserTool, and createWebSearchTool. When a directory is set, file and path operations stay inside it and its subdirectories.
Example
var workingDir = ".";
var readTool = createReadFileTool(workingDir);
var writeTool = createWriteFileTool(workingDir);
conversation.addTool(readTool);
conversation.addTool(writeTool);
conversation.addUserMessage("Read example.txt and summarize it.");
var response = conversation.send(); // LLM will call read_file tool automatically
18.7 Agent Class
The Agent class represents an autonomous agent with a specific role and instructions.
Constructor
// With default local LLM (auto-download from Hugging Face)
var agent = new Agent(name, role, instructions);
// With a custom client
var agent = new Agent(name, role, instructions, client);
Methods
think(prompt)→object: Process a prompt and return response (handles tool calls automatically)addTool(tool)oraddTool(toolName): Add a tool the agent can useaddToolByName(toolName): Add a registered tool by nameaddAllTools(): Add all registered tools to the agentaddSubAgent(subAgent, toolDescription): Add another agent as a tool with a simplified descriptiongetAvailableTools()→array: Get list of available tool namesgetConversation()→Conversation: Get the agent's conversation objectreset(): Reset agent's conversation history (tools are preserved)enableMemory(),enableMemory(path),useMemory(memory),saveMemory(path?),remember(fact, context?),getMemory(): GraphMemory integration (see 19. GraphMemory).enableMemory(path)shares one GraphMemory instance across agents using the same path.setMemoryScope(scope),setMemoryScopeParent(parentScope),setMemoryScopeHierarchy(scopes): Control hierarchical memory visibility duringthink()(e.g.chat:123→project:foo→org:acme→global). UsesetMemoryScopeHierarchyfor multi-level config; otherwise parent scope falls back toMALDA_MEMORY_SCOPE_PARENT.setMemoryRerank(enabled, mode?, modelPath?, topK?): Enable GraphMemory rerank duringthink()memory queries (rerankMode:llm,cross, oronnx). Env fallbacks:MALDA_MEMORY_RERANK,MALDA_MEMORY_RERANK_MODE,MALDA_MEMORY_RERANK_MODEL_PATH.setAutoRememberOnThink(enabled): Whentrue, eachthink()stores the full prompt and response in memory (defaulttrue)addMemoryProgressTools(): Addsremember_progressandrecall_progresstools for explicit LLM-controlled notes
Example
var client = new OpenRouterClient();
var agent = new Agent(
"CodeMaster",
"senior software engineer",
"You write clean, efficient code. Always include comments and error handling.",
client
);
agent.addTool(createReadFileTool("."));
agent.addTool(createWriteFileTool("."));
var response = agent.think("Read example.txt and add a comment to the add function.");
print(response.content);
18.8 Custom Tools with @Tool Decorator
schema and validate() before side effects. Mark normalize/derive helpers @pure() and impure handlers @effects(...). Offline golden: Examples/Agents/agent_governance_golden.malda (see also 9.7 Decorators).
MALDA supports defining custom tools for LLMs using the @Tool decorator on functions.
Decorator Syntax
@Tool(name, description, schema?)
Example
@Tool("calculate_sum", "Adds two numbers together")
function calculateSum(a, b) {
return int(a) + int(b);
}
var agent = new Agent("Assistant", "Helper", "You are helpful", client);
agent.addTool("calculate_sum");
var response = agent.think("What is 15 + 27?");
print(response.content);
Constructing a tool with new Tool
You can also build a Tool instance at runtime and pass a handler function. The usual pattern is a single args parameter (the LLM argument object). Call tool.execute(args) to invoke the handler directly, or agent.addTool(tool) for agent loops.
function echoHandler(args) {
return "got " + string(args.message);
}
var echoTool = new Tool(
"echo_message",
"Echoes a message back",
{
"type": "object",
"properties": {
"message": { "type": "string", "description": "Text to echo" }
},
"required": ["message"]
},
echoHandler
);
print(echoTool.execute({ "message": "hello" })); // got hello
agent.addTool(echoTool);
See Examples/Agents/secondbrain_semantic.malda (find_related_notes) for a GraphMemory-backed example used in ASK when tools are enabled.
18.9 CodingAgent
The CodingAgent class is a specialized agent for coding tasks that automatically includes all file operation tools.
Included Tools
CodingAgent automatically includes the following file operation tools and command execution tool:
read_file- Read file contentswrite_file- Write content to filesreplace_in_file- Replace text in files (single replacement)edit_file- Apply multiple edits to a file (multiple replacements)insert_at_line- Insert content at specific line numbersgrep- Search for patterns in fileslist_directory- List directory contentsrun_command- Execute shell commands (e.g.,dotnet,npm,python) with safety checks to prevent dangerous commands
Constructor
// Simplest form - uses default local LLM (auto-download from Hugging Face)
var agent = new CodingAgent(name, role, instructions);
// With custom working directory
var agent = new CodingAgent(name, role, instructions, workingDirectory);
// With custom client
var agent = new CodingAgent(name, role, instructions, client);
// With custom client and working directory
var agent = new CodingAgent(name, role, instructions, client, workingDirectory);
Example
var coderAgent = new CodingAgent(
"CodeMaster",
"senior software engineer",
"You write clean, efficient code."
);
// Agent already has all file tools - ready to use!
var response = coderAgent.think("Read example.txt and add error handling.");
print(response.content);
18.10 GitAgent
The GitAgent class is a specialized agent for git operations that automatically includes all git tools.
Included Tools
GitAgent automatically includes the following git operation tools:
git_status- Check repository statusgit_add- Stage filesgit_commit- Create commitsgit_log- View commit historygit_diff- View changesgit_branch- Manage branches (list/create only - deletion disabled for safety)git_checkout- Switch branchesgit_push- Push to remotegit_pull- Pull from remote
Constructor
// Simplest form - uses default local LLM (auto-download from Hugging Face)
var agent = new GitAgent(name, role, instructions);
// With custom working directory
var agent = new GitAgent(name, role, instructions, workingDirectory);
// With custom client
var agent = new GitAgent(name, role, instructions, client);
// With custom client and working directory
var agent = new GitAgent(name, role, instructions, client, workingDirectory);
Example
var gitAgent = new GitAgent(
"GitHelper",
"git assistant",
"You help manage git repositories."
);
// Agent already has all git tools - ready to use!
var response = gitAgent.think("Check git status, stage all changes, and commit with message 'Update code'");
print(response.content);
18.11 HumanAgent
The HumanAgent class is a specialized agent for human interaction that automatically includes the ask_user tool. This makes it easy to create agents that need to interact with humans for clarification, confirmation, or additional information.
Included Tools
HumanAgent automatically includes the following user interaction tool:
ask_user- Ask questions to the user for clarification, confirmation, or additional information
Constructor
// Simplest form - uses default local LLM (auto-download from Hugging Face)
var agent = new HumanAgent(name, role, instructions);
// With custom working directory
var agent = new HumanAgent(name, role, instructions, workingDirectory);
// With custom client
var agent = new HumanAgent(name, role, instructions, client);
// With custom client and working directory
var agent = new HumanAgent(name, role, instructions, client, workingDirectory);
Example
// Simplest usage - HumanAgent uses default local LLM
var humanAgent = new HumanAgent(
"Assistant",
"helpful assistant",
"You help users by asking clarifying questions when needed."
);
// Agent already has ask_user tool - ready to use!
var response = humanAgent.think("Ask the user what their favorite programming language is, then provide a brief explanation of that language.");
print(response.content);
Using HumanAgent as a Subagent (Human-in-the-Loop Pattern)
One of the key use cases for HumanAgent is as a subagent in complex multi-agent workflows. This enables human-in-the-loop patterns where orchestrator agents can delegate human interaction tasks to a specialized HumanAgent.
var client = new OpenRouterClient();
// Create a HumanAgent for user interaction
var humanAgent = new HumanAgent(
"HumanInterface",
"user interaction specialist",
"You handle all interactions with human users. " +
"Ask clear, concise questions when clarification is needed. " +
"Request approval before making destructive changes.",
client
);
// Create an orchestrator that can delegate to specialists
var orchestrator = new CodingAgent(
"Orchestrator",
"project coordinator",
"You coordinate code changes. " +
"When you need user approval or clarification, use the HumanInterface tool. " +
"Always ask for confirmation before deleting files or making breaking changes.",
client
);
// Add HumanAgent as a subagent
orchestrator.addSubAgent(
humanAgent,
"Handles user interaction, clarification requests, and approval confirmations. " +
"Use this when you need to ask the user a question or get their approval."
);
// Now orchestrator can automatically request human input when needed
var response = orchestrator.think(
"Refactor the codebase to use dependency injection. " +
"Ask the user which design pattern they prefer before proceeding."
);
Benefits of Human-in-the-Loop Pattern:
- Safety: Orchestrators can request approval before destructive operations
- Clarification: Agents can ask for missing information instead of guessing
- Flexibility: Complex workflows can pause for human decision-making
- Separation of Concerns: Human interaction logic is isolated in the HumanAgent
- Reusability: Same HumanAgent can be used by multiple orchestrators
18.12 DevAgent
The DevAgent class is a specialized agent for full development workflows that automatically includes all file operation tools, git tools, command execution tools, and optional code analysis tools.
Included Tools
DevAgent automatically includes the following tools:
- File tools:
read_file,write_file,replace_in_file,insert_at_line,edit_file,grep,list_directory - Git tools:
git_status,git_add,git_commit,git_log,git_diff,git_branch,git_checkout,git_push,git_pull - Execution tools:
run_command - Interaction tools:
ask_user - Structured task planning:
submit_plan- Agents can submit a plan (steps withid,description, optionaldependsOn) before or during execution. - Optional analysis:
get_symbols(whenincludeSymbolsis true)
Constructor
// Simplest form - uses default local LLM (auto-download from Hugging Face)
var agent = new DevAgent(name, role, instructions);
// With custom working directory
var agent = new DevAgent(name, role, instructions, workingDirectory);
// With custom client
var agent = new DevAgent(name, role, instructions, client);
// With custom client and working directory
var agent = new DevAgent(name, role, instructions, client, workingDirectory);
// With includeSymbols enabled (for code analysis)
var agent = new DevAgent(name, role, instructions, client, workingDirectory, true);
Example
// Simplest usage - DevAgent uses default local LLM
var devAgent = new DevAgent(
"DevMaster",
"senior developer",
"You write, test, compile, and commit code."
);
// Agent already has all development tools - ready to use!
// Can handle full workflow: edit, test, commit
var response = devAgent.think("Read example.txt, add error handling, and commit the changes");
print(response.content);
Example with Code Analysis
// DevAgent with getSymbols enabled for better code understanding
var devAgent = new DevAgent(
"CodeAnalyzer",
"code refactoring expert",
"You analyze code structure and make targeted improvements.",
null, // default client
".", // working directory
true // includeSymbols - enables get_symbols tool
);
// Agent can now analyze code structure before making edits
var response = devAgent.think("Analyze the codebase structure and refactor the main function");
print(response.content);
Example with Custom Client
var client = new OpenRouterClient("openai/gpt-4");
var devAgent = new DevAgent(
"FullStackDev",
"full-stack developer",
"You develop complete features: code, test, compile, and commit.",
client,
"./project",
false // includeSymbols
);
// Agent can handle complete development workflow
var response = devAgent.think("Implement a new feature: add a user authentication system");
print(response.content);
18.13 Structured task planning
MALDA supports breaking complex tasks into ordered steps and executing them via an agent. A plan is an object { steps: [{ id, description, dependsOn? }, ...], planId?, taskSummary? }. Each step has a unique id (string), description (string), and optional dependsOn (array of step ids that must complete first).
- decomposeTask(instruction, client?) – Uses an LLM to turn a high-level instruction into a plan. Optional second argument is an LLM client; if omitted, the default local LLM (Qwen/Qwen2.5-0.5B-Instruct, auto-downloaded as a GGUF build from Hugging Face) is used. Returns a plan object or
{ error }on failure. - executePlan(plan, agent) – Validates the plan, topo-sorts steps by
dependsOn, then runsagent.think(step.description)for each step in order. Returns{ planId, completed, failed, results }. - submit_plan tool – Agents can call this tool to submit a plan (e.g. after a first "think" that produces the plan). DevAgent includes this tool by default. Parameters:
plan(object withsteps) orsteps(array), optionaltaskSummary. Returns{ accepted: true, planId, stepCount }or{ accepted: false, error }. - PromptInstance metadata – When calling
agent.think(promptInstance), optional fieldsmodel,temperature,maxTokens, andtools(array of tool names) apply to that single LLM request. - DevAgent code memory –
devAgent.enableCodeMemory(path?, scope?)loads GraphMemory at.dev-agent-memoryunder the workdir (by default), sets scopecode:<workdir-name>, and registersindex_code_file/find_code_relationshipstools.devAgent.indexCodebase(extensions?)bulk-indexes source files (default:.cs,.malda,.js,.ts,.py,.html,.css,.json,.md).
// Decompose then execute from script
var plan = decomposeTask("Add unit tests for getSymbols and run the test suite");
var agent = new DevAgent("Dev", "developer", "You are a developer.", null, ".");
var result = executePlan(plan, agent);
print(result.completed);
print(result.results);
18.14 MALDACodingAgent
The MALDACodingAgent class is a specialized agent for MALDA script development and toolchain operations that automatically includes all file operation tools and MALDA execution/compilation tools.
Included Tools
MALDACodingAgent automatically includes the following tools:
- File tools:
read_file,write_file,replace_in_file,insert_at_line,edit_file,grep,list_directory - MALDA tools:
run_malda,compile_malda
Constructor
// Simplest form - uses default local LLM (auto-download from Hugging Face)
var agent = new MALDACodingAgent(name, role, instructions);
// With custom working directory
var agent = new MALDACodingAgent(name, role, instructions, workingDirectory);
// With custom client
var agent = new MALDACodingAgent(name, role, instructions, client);
// With custom client and working directory
var agent = new MALDACodingAgent(name, role, instructions, client, workingDirectory);
Example
// Simplest usage - MALDACodingAgent uses default local LLM
var maldaAgent = new MALDACodingAgent(
"MALDADeveloper",
"MALDA script developer",
"You write, test, and compile MALDA scripts. Always verify your changes work by running the code."
);
// Agent already has all MALDA development tools - ready to use!
// Can handle MALDA workflow: edit, run, compile, test
var response = maldaAgent.think("Read example.malda, add error handling, test it with run_malda, and compile it");
print(response.content);
Example with Custom Client
var client = new OpenRouterClient("openai/gpt-4");
var maldaAgent = new MALDACodingAgent(
"MALDAMaster",
"MALDA expert",
"You develop MALDA scripts with best practices and thorough testing.",
client
);
// Agent can handle MALDA development workflow
var response = maldaAgent.think("Create a new MALDA script that calculates fibonacci numbers, test it, and compile it");
Example with Working Directory
var client = new OpenRouterClient("openai/gpt-4");
var maldaAgent = new MALDACodingAgent(
"MALDAHelper",
"MALDA assistant",
"You help develop and test MALDA scripts.",
client,
"./malda-scripts" // Restrict operations to ./malda-scripts directory
);
// Agent can only access files within ./malda-scripts
var response = maldaAgent.think("List all MALDA files and improve error handling in each one");
When to Use MALDACodingAgent vs DevAgent vs CodingAgent
- Use
MALDACodingAgentwhen you need MALDA-specific development: file operations + MALDA execution/compilation (focused on MALDA script development) - Use
DevAgentwhen you need a complete development workflow: file operations, git operations, command execution, and user interaction - Use
CodingAgentwhen you only need file operations and command execution (lighter weight, fewer tools, for general coding tasks) - Use
GitAgentwhen you only need git operations
18.15 Agent Dashboard
MALDA agents automatically report their activities to a central dashboard without requiring any modifications to MALDA scripts. This enables monitoring and observability across all agents, even when they run in different executables or processes.
Overview
The agent dashboard feature provides:
- Automatic Reporting: All agents automatically report lifecycle events, think() calls, and tool executions
- Zero Configuration: Works out of the box with default localhost dashboard
- Cross-Process Support: Agents from different executables are distinguished by process ID
- Error Tolerant: Dashboard failures never affect agent execution
- Non-Blocking: All reporting is asynchronous and fire-and-forget
Configuration
The dashboard URL is configured via the SPL_AGENT_DASHBOARD_URL environment variable:
# Set custom dashboard URL
export SPL_AGENT_DASHBOARD_URL="http://my-dashboard.example.com/api/agent/status"
# If not set, defaults to:
# http://localhost:8080/api/agent/status
Note: If the environment variable is set but doesn't end with /api/agent/status, the endpoint path is automatically appended.
Reported Events
Agents automatically report the following events:
- Agent Created (
agent_created)- Reported when an agent is initialized
- Includes: agent name, role, process ID, timestamp
- Think Operation (
think)- Reported when
agent.think()is called - Includes: agent name, prompt (truncated to 500 chars), prompt length, process ID, timestamp
- Reported when
- Tool Call (
tool_call)- Reported when tools are executed
- Includes: agent name, tool name, success status, error message (if failed), process ID, timestamp
- Agent Reset (
agent_reset)- Reported when
agent.reset()is called - Includes: agent name, process ID, timestamp
- Reported when
Dashboard Payload Format
All events are sent as HTTP POST requests with JSON payloads:
{
"agentId": "MyAgent",
"processId": 12345,
"eventType": "think",
"timestamp": "2026-01-15T10:30:00.123Z",
"data": {
"prompt": "Analyze this code...",
"promptLength": 20
}
}
Payload Fields:
agentId(string): The name of the agentprocessId(integer): Process ID to distinguish agents from different executableseventType(string): Type of event (agent_created,think,tool_call,agent_reset)timestamp(string): UTC timestamp in ISO 8601 formatdata(object): Event-specific data
Event-Specific Data:
- agent_created:
{ "role": "..." } - think:
{ "prompt": "...", "promptLength": 123 } - tool_call:
{ "toolName": "...", "success": true/false, "errorMessage": "..." } - agent_reset:
{}
Dashboard Endpoint Requirements
The dashboard server should implement a POST endpoint that accepts JSON payloads:
- URL: Configured via
SPL_AGENT_DASHBOARD_URL(default:http://localhost:8080/api/agent/status) - Method: POST
- Content-Type:
application/json - Body: JSON object with agent status information (see payload format above)
Example Dashboard Server (MALDA)
var server = new HttpServer(8080);
var agentStatuses = {};
@POST("/api/agent/status")
function receiveAgentStatus(body) {
var agentId = body.agentId;
var processId = body.processId;
var eventType = body.eventType;
var timestamp = body.timestamp;
var data = body.data;
// Store or process agent status
if (agentStatuses[agentId] == null) {
agentStatuses[agentId] = {};
}
agentStatuses[agentId][eventType] = {
"processId": processId,
"timestamp": timestamp,
"data": data
};
return {"status": 200, "message": "Status received"};
}
@GET("/api/agents")
function getAllAgents() {
return {"status": 200, "agents": agentStatuses};
}
@PAGE("/")
function dashboardPage() {
// Generate HTML dashboard showing all agents
return generateDashboardHTML(agentStatuses);
}
server.start();
print("Agent Dashboard running at http://localhost:8080");
Usage
No MALDA script Changes Required!
Agents automatically report to the dashboard. Simply create agents as usual:
var client = new OpenRouterClient();
var agent = new Agent(
"MyAgent",
"coder",
"You write code.",
client
);
// Agent automatically reports:
// 1. agent_created event when initialized
// 2. think event when think() is called
// 3. tool_call events when tools are executed
// 4. agent_reset event when reset() is called
var response = agent.think("Write a hello world function");
// Dashboard receives think and tool_call events automatically
Error Handling
The dashboard reporting system is designed to be completely non-intrusive:
- Network Failures: If the dashboard is unreachable, agents continue working normally
- Timeout Protection: HTTP requests timeout after 2 seconds to prevent hanging
- Silent Failures: All dashboard errors are caught and ignored - they never affect agent execution
- Fire-and-Forget: All reporting is asynchronous and non-blocking
Benefits
- Observability: Monitor all agents across your system in real-time
- Debugging: Track agent activities and tool usage patterns
- Analytics: Collect metrics on agent performance and tool usage
- Multi-Process Support: Track agents running in different executables via process ID
- Zero Overhead: No performance impact on agent execution
18.16 Multi-Agent Orchestration
Multiple agents can work together, each with specialized roles and tools.
Parallel Agent Workflow
Agents can work independently on different tasks:
var readerAgent = new Agent("Reader", "file reader", "You read and analyze files.", client);
readerAgent.addTool(createReadFileTool("."));
var editorAgent = new Agent("Editor", "code editor", "You edit code files.", client);
editorAgent.addTool(createReadFileTool("."));
editorAgent.addTool(createReplaceInFileTool("."));
var code = readerAgent.think("Read main.MALDA and summarize it.");
var edited = editorAgent.think("Read main.MALDA and add error handling.");
Hierarchical Agent Systems with addSubAgent
The addSubAgent method allows you to create hierarchical agent systems where an orchestrator agent can delegate tasks to specialized subagents. This is useful when you want to separate detailed internal instructions from the tool description shown to the orchestrator.
addSubAgent uses a simplified, tool-focused description rather than the agent's full internal instructions, keeping the orchestrator's context clean and focused.
Syntax
orchestrator.addSubAgent(subAgent, toolDescription);
subAgent: AnAgentinstance to be used as a tooltoolDescription: A concise description of what the subagent does (shown to the orchestrator)
Example
var client = new OpenRouterClient();
// Create specialized subagents with detailed internal instructions
var codeReviewer = new Agent(
"CodeReviewer",
"code review specialist",
"You are an expert code reviewer. When reviewing code, you should:\n" +
"1. Check for bugs and logical errors\n" +
"2. Review code style and best practices\n" +
"3. Suggest improvements\n" +
"4. Consider performance implications\n" +
"5. Check for security vulnerabilities\n" +
"Provide detailed feedback with line numbers and specific suggestions.",
client
);
var tester = new Agent(
"Tester",
"testing specialist",
"You are an expert in software testing. When asked to write tests:\n" +
"1. Analyze the code to understand what needs to be tested\n" +
"2. Write comprehensive test cases covering edge cases\n" +
"3. Include both positive and negative test cases\n" +
"4. Ensure tests are clear and maintainable\n" +
"5. Consider boundary conditions and error handling",
client
);
// Create orchestrator agent
var orchestrator = new Agent(
"Orchestrator",
"project coordinator",
"You coordinate tasks between specialized agents. " +
"When you need code reviewed, use the CodeReviewer tool. " +
"When you need tests written, use the Tester tool. " +
"Delegate work to the appropriate specialist agent.",
client
);
// Add subagents with simplified, tool-focused descriptions
orchestrator.addSubAgent(
codeReviewer,
"Reviews code for bugs, style issues, and improvements. Provide the code to review as the prompt."
);
orchestrator.addSubAgent(
tester,
"Writes comprehensive test cases for code. Provide the code to test as the prompt."
);
// Orchestrator can now delegate to subagents
var code = "function add(a, b) {\n return a + b;\n}";
var response = orchestrator.think(
"I have this code: " + code + "\n\n" +
"Please have it reviewed and then write tests for it."
);
print(response.content);
When the orchestrator calls a subagent tool, it passes a prompt parameter, and the subagent's think() method is called automatically. The subagent's response is returned to the orchestrator.
See Also
- 9. Functions - @Tool decorator syntax
- 10. Prompts - Prompt templates and
agent.think - 20. MCP Server - Host-launched STDIO tools
- 12. Input/Output — file tools consume
io.*and capability tokens
18.17 Using Agents with Actors
Agents and actors are complementary features in MALDA. While agents are classes designed for synchronous AI interactions, actors provide concurrent, message-based processing. Combining them enables powerful patterns for distributed AI systems.
- Most agent usage is synchronous and straightforward
- Tool execution is naturally synchronous
- Agents can be easily stored in actor state when concurrency is needed
- This provides flexibility without forcing actor complexity for simple cases
Agent as Actor State
The most common pattern is storing an agent instance as a field in an actor. This allows the actor to use AI capabilities while maintaining isolated state and concurrent processing:
actor AIAssistant {
var agent;
var conversationHistory = [];
function AIAssistant(client) {
// Create an agent and store it in actor state
agent = new Agent(
"Assistant",
"helpful assistant",
"You help users with their questions.",
client
);
}
on ask(question) {
// Use the agent to process questions
var response = agent.think(question);
append(conversationHistory, question);
append(conversationHistory, response.content);
print(response.content);
}
on getHistory() {
print(conversationHistory);
}
}
// Spawn multiple AI assistants with isolated state
var client = new OpenRouterClient();
var assistant1 = spawn AIAssistant(client);
var assistant2 = spawn AIAssistant(client);
send assistant1.ask("What is 2+2?");
send assistant2.ask("What is the weather?");
Concurrent Agent Orchestration
Actors enable true concurrent processing of multiple agents. While agents are synchronous, wrapping them in actors allows parallel execution:
actor CodingAgentActor {
var agent;
var taskQueue = [];
function CodingAgentActor(client, workingDir) {
agent = new CodingAgent(
"Coder",
"software engineer",
"You write clean, efficient code.",
client,
workingDir
);
}
on processTask(task) {
var result = agent.think(task);
print($"Task completed: {result.content}");
}
}
// Spawn multiple coding agents for parallel processing
var client = new OpenRouterClient();
var agent1 = spawn CodingAgentActor(client, "./project1");
var agent2 = spawn CodingAgentActor(client, "./project2");
var agent3 = spawn CodingAgentActor(client, "./project3");
// Send tasks concurrently - all process in parallel
send agent1.processTask("Refactor the user authentication module");
send agent2.processTask("Add error handling to the API");
send agent3.processTask("Write unit tests for the database layer");
Multi-Agent Actor System
You can orchestrate multiple specialized agents within a single actor to create complex workflows:
actor AgentOrchestrator {
var coderAgent;
var reviewerAgent;
var testerAgent;
function AgentOrchestrator(client) {
coderAgent = new CodingAgent("Coder", "developer", "Write clean code.", client);
reviewerAgent = new Agent("Reviewer", "code reviewer", "Review code for quality.", client);
testerAgent = new Agent("Tester", "QA engineer", "Write comprehensive tests.", client);
}
on develop(feature) {
// Step 1: Code
var code = coderAgent.think($"Write code for: {feature}");
// Step 2: Review
var review = reviewerAgent.think($"Review this code: {code.content}");
// Step 3: Test
var tests = testerAgent.think($"Write tests for: {code.content}");
return {
"code": code.content,
"review": review.content,
"tests": tests.content
};
}
}
var client = new OpenRouterClient();
var orchestrator = spawn AgentOrchestrator(client);
send orchestrator.develop("User authentication system");
Agent State Persistence via Actors
Actors can manage and persist agent conversation state, enabling long-running agent sessions:
actor AgentStateManager {
var agents = {}; // Dictionary of agent instances
var conversations = {}; // Conversation history per agent
on createAgent(name, role, instructions, client) {
var agent = new Agent(name, role, instructions, client);
agents[name] = agent;
conversations[name] = [];
}
on queryAgent(agentName, question) {
if (has(agents, agentName)) {
var agent = agents[agentName];
var response = agent.think(question);
append(conversations[agentName], {
"question": question,
"answer": response.content
});
return response.content;
}
return "Agent not found";
}
on getHistory(agentName) {
return conversations[agentName];
}
}
var client = new OpenRouterClient();
var stateManager = spawn AgentStateManager();
send stateManager.createAgent("Helper", "assistant", "You help users", client);
send stateManager.queryAgent("Helper", "What is MALDA?");
Benefits of Combining Agents and Actors
- Concurrency: Multiple agents can process tasks in parallel without blocking
- State Isolation: Each actor maintains its own agent instance and conversation history
- Scalability: Spawn many agent actors for distributed AI processing
- Fault Tolerance: If one agent actor fails, others continue independently
- Message-Based Coordination: Use actor messaging to coordinate agent workflows
- Long-Running Sessions: Actors can maintain agent state across multiple interactions
See Also
- 17. Actors - Actor model and message passing
- 17.5 Actor State Isolation - Understanding actor state
18.18 Ralph Wiggum (PRD-driven autonomous loop)
The repository includes Examples/RalphWiggum/RalphWiggum.malda, a production-ready autonomous loop for PRD-driven development. It uses DevAgent, a checklist PRD (PRD.md), post-iteration validation, resume state, and GraphMemory for continuity across iterations when conversation history is reset each round. See Examples/RalphWiggum/README.md for prerequisites and the Snake demo.
Running
dotnet run --project MaldaLang -- Examples/RalphWiggum/RalphWiggum.malda
# Or from a project directory with PRD.md:
# set MALDA_RALPH_WORKDIR=Examples/RalphWiggum/snake-demo
# Examples\RalphWiggum\snake-demo\run-ralph.bat
Persisted artifacts (in workdir)
.ralph-state.json— iteration history, validation notes, resume checkpoint (lastSuccessfulIteration,failedIterations, token totals when available).ralph-memory.graph.json,.ralph-memory.metadata.json,.ralph-memory.vectordb.bin— GraphMemory persistenceralph-run-report.json/ralph-run-report.html— run summary whenMALDA_RALPH_REPORTis enabledPRD.md— feature checklist with[ ],[TODO],[DONE], optional[P0]priority,(depends: …), andAcceptance:lines
Memory model
Ralph attaches shared GraphMemory to the agent via setupRalphGraphMemory() and:
- Initializes with
MALDA_MEMORY_EMBED(hash,bow, orllama) and loads.ralph-memory.*from the workdir - Scopes memory with
agent.setMemoryScope("ralph:{project}")(override viaMALDA_RALPH_MEMORY_SCOPE) - Reindexes
PRD.mdat startup and seeds facts from.ralph-interview-brief.jsonwhen present - Runs
maintainRalphMemory()after each iteration:consolidate/reflect,prune,enforceLimits(default on whenMAX_ITER > 5) - Disables auto-remember of the full prompt by default (
setAutoRememberOnThink(false)); setMALDA_RALPH_AUTO_REMEMBER=trueto restore legacy behavior - Registers
remember_progress/recall_progressviaaddMemoryProgressTools() - After each iteration, saves a structured summary (phase, validation, files, tool count) with metadata
- Indexes modified files with
memory.analyzeFile()where applicable agent.think()injects hybrid memory (vector + lexical + synapse + MMR). Optional phase-scoped block:MALDA_RALPH_MEMORY_PHASE_QUERY=true- Includes
memory.stats()inralph-run-report.jsonwhen reporting is enabled
Inspect Ralph memory from the workdir: malda memory stats --path .ralph-memory. See Examples/RalphWiggum/README.md for MALDA_RALPH_MEMORY_* variables.
Key environment variables
MALDA_RALPH_WORKDIR,MALDA_RALPH_WORKTREE,MALDA_RALPH_PROJECT_REL,MALDA_RALPH_PRD,MALDA_RALPH_MAX_ITER,MALDA_RALPH_TASKMALDA_RALPH_RESET_EACH—true,false, orphase(reset conversation when PRD phase changes; defaulttrue)MALDA_RALPH_RESUME,MALDA_RALPH_RESUME_POLICY(all|success-only),MALDA_RALPH_VALIDATE,MALDA_RALPH_VALIDATE_DEPTH,MALDA_RALPH_VALIDATE_CMDMALDA_RALPH_PRD_STRICT,MALDA_RALPH_REQUIRE_SIGNAL,MALDA_RALPH_PLAN_ONLY,MALDA_RALPH_PREFLIGHTMALDA_RALPH_REPORT,MALDA_RALPH_NOTIFY,MALDA_RALPH_MAX_PHASE_RETRIES,MALDA_RALPH_ABORT_ON_STALLMALDA_RALPH_GIT_COMMIT,MALDA_RALPH_AUTO_REMEMBER(defaultfalse)MALDA_AGENT_THINK_TIMEOUT_MS/MALDA_RALPH_ITER_TIMEOUT_MS— per-iteration think deadline- Verbose CLI:
MALDA_AGENT_VERBOSE(defaulttruein Ralph),MALDA_AGENT_RICH,MALDA_AGENT_LLM_THINKING,MALDA_AGENT_LLM_STREAM— see 18.5 verbose logging OPENROUTER_API_KEYorproviders.openrouterin~/.malda/config.json
Implementation modules: Examples/RalphWiggum/ralph/*.malda (see ralph/ARCHITECTURE.md). Full env var list and git-worktree workflow: Examples/RalphWiggum/README.md.
Completion signals
Ralph accepts TASK_COMPLETE (case-insensitive), a dedicated RALPH_DONE line, JSON {"ralph":"done"}, or auto-complete when every PRD item is [DONE] and validation passes (unless MALDA_RALPH_REQUIRE_SIGNAL=true).
Custom validation hooks
After structural checks, Ralph may run MALDA_RALPH_VALIDATE_CMD, or a project-local .ralph-validate.sh / .ralph-validate.bat / (or .ralph-validate.malda) in the workdir.
Execution strategy
Ralph prompts the agent to complete one PRD checklist item per iteration in as few LLM rounds as possible: batch read-only exploration (list_directory, grep, read_file) in one response, then batch edits and verification. This pairs with parallel read-only tool execution (18.5) and streamed [think] feedback during verbose runs.
See also 19. GraphMemory for remember metadata, getRecent, and hybrid query options.
reportRalphStatus(agentName, phase, iteration, maxIter, prdPercent, validationOk?, elapsedMs?, promptTokens?, completionTokens?, costUsd?) publishes one iteration of progress to the agent dashboard. The first five arguments are required; the rest default to false, 0, 0, 0, and 0.0.
reportRalphStatus("Ralph", "implement", 3, 10, 45, true, 12000, 1800, 640, 0.02);