MALDA™ Reference Manual

The AI-First Programming Language - Version 1.0.11

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

Real-World Risk Scenarios

1. Data Loss and File System Damage

2. Unauthorized Code Execution

3. Git Repository Corruption

4. Unauthorized Access and Information Disclosure

5. Infinite Loops and Resource Exhaustion

6. Cascading Failures in Multi-Agent Systems

7. Production System Modification

8. Malicious Code Injection

Mitigation Strategies

Best Practices

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

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");
Automatic Configuration: OpenRouterClient automatically reads the API key from the 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

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:

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).

VariableDefaultPurpose
MALDA_AGENT_VERBOSEfalse (Ralph: true)Enable [llm] / [tool] logging
MALDA_RALPH_VERBOSEAlias for MALDA_AGENT_VERBOSE
MALDA_AGENT_RICHtrueColored Spectre.Console output (auto-off when redirected)
MALDA_AGENT_TOOL_DETAILcompactcompact or full tool logs
MALDA_AGENT_LLM_THINKINGcompact[think] output: off, compact, or full
MALDA_AGENT_LLM_STREAMtrueOpenAI SSE streaming for HTTP clients; live [think] when thinking is enabled
MALDA_AGENT_LLM_PREVIEWcompactFinal-response preview when streaming/thinking is off
MALDA_AGENT_STATUS_EVERY4Repeat 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();
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

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

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

Governance defaults: Validate tool / LLM-shaped payloads with a 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:

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:

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:

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:

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:

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).

// 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:

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

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:

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:

  1. Agent Created (agent_created)
    • Reported when an agent is initialized
    • Includes: agent name, role, process ID, timestamp
  2. Think Operation (think)
    • Reported when agent.think() is called
    • Includes: agent name, prompt (truncated to 500 chars), prompt length, process ID, timestamp
  3. Tool Call (tool_call)
    • Reported when tools are executed
    • Includes: agent name, tool name, success status, error message (if failed), process ID, timestamp
  4. Agent Reset (agent_reset)
    • Reported when agent.reset() is called
    • Includes: agent name, process ID, timestamp

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:

Event-Specific Data:

Dashboard Endpoint Requirements

The dashboard server should implement a POST endpoint that accepts JSON payloads:

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:

Benefits

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.

Key Benefit: 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);

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

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.

Design Decision: Agents are implemented as classes rather than native actors because:

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

See Also

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)

Memory model

Ralph attaches shared GraphMemory to the agent via setupRalphGraphMemory() and:

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

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);