32. Personal Assistant and CLI
MALDA provides a built-in personal assistant and a set of CLI commands for configuration, scheduled tasks, and status. These features use a standard config directory ~/.malda and an optional config file so you can run an AI assistant from the command line without writing a script.
32.1 Quick start
- Run
malda onboard(ormalda onboard --download-rerank --download-local-llama) to create~/.malda,skills/,memory/, and a starterconfig.jsonwithagents.memory,channels.telegram, and provider placeholders. - Set
OPENROUTER_API_KEYor addproviders.openrouter.apiKeyin~/.malda/config.json. - Optional:
malda memory download-rerankinstalls the ONNX cross-encoder under~/.malda/models/cross-encoderforagents.memory.rerankMode: onnx. - Run
malda agentfor interactive chat, ormalda agent -m "Your question"for a one-shot reply.
32.2 Commands
32.2.1 malda agent
Runs the default assistant script in interactive or one-shot mode.
malda agent— Starts an interactive loop: prompts withYou:, reads your message, calls the agent, and prints the response. Typeexitorquit(or empty line) to exit.malda agent -m "message"ormalda agent --message "message"— Sends the message once to the assistant, prints the response, and exits.
The assistant script is resolved in this order:
- Path in the
MALDA_AGENT_SCRIPTenvironment variable (if the file exists). ~/.malda/assistant.malda.Examples/Assistant/assistant.maldarelative to the current directory or the executable (e.g. when run from the repo).
If no script is found, the CLI prints an error and exits.
32.2.2 malda onboard
Initializes the MALDA config directory and a guided starter config.
- Creates
~/.malda,~/.malda/skills/, and~/.malda/memory/if they do not exist. - Creates
~/.malda/config.jsonwithproviders,channels.telegram,agents.defaults,agents.memory(embed, rerank ONNX path), andtools.web.searchif the file does not exist. --download-rerank— downloadsmodel.onnxandvocab.txtto~/.malda/models/cross-encoder.--download-local-llama— downloads the default GGUF model and setsproviders.local_llama.modelPathwhen empty.
Run this once before using the assistant, gateway, or cron. The command prints next-step hints (API keys, Telegram, ONNX rerank, malda doctor).
32.2.3 malda status
Prints the current assistant setup and runtime health. Use malda status --json for machine-readable output (config, channels, skills, gateway, memory, cron jobs).
- MALDA home and config path; whether
~/.malda/config.jsonexists. - Whether an OpenRouter API key is set (environment or config), default model, backend, and local llama model path.
- Telegram channel configured (
TELEGRAM_BOT_TOKENorchannels.telegram.botToken). - Skills count in
~/.malda/skills/. - Gateway process state (
~/.malda/gateway.pid; stale pid files are removed). - GraphMemory stats when
~/.malda/memory/assistantexists (nodes, edges, last reflect). - Cron jobs from
~/.malda/cron.json(id, name, scope, message, cron expression).
32.2.4 malda gateway
Long-running process for Telegram and optional in-process cron scheduling.
malda gateway— Starts Telegram long polling with the default assistant script. Writes~/.malda/gateway.pidand refuses to start if another gateway is already running.malda gateway stop— Stops a running gateway process and removes~/.malda/gateway.pid(also cleans stale pid files).malda gateway -c telegram— Same as above (Telegram is the only supported channel today).malda gateway --no-cron— Disable the built-in cron scheduler (use system Task Scheduler viamalda cron installinstead).
The gateway runs the same assistant.malda as malda agent -c telegram, but also polls ~/.malda/cron.json every minute and spawns malda agent -m "..." for due jobs (with per-job memory scope). Requires a Telegram bot token in config or TELEGRAM_BOT_TOKEN.
Gateway alerts: set channels.telegram.notifyChatId (or MALDA_GATEWAY_NOTIFY_CHAT_ID) to receive Telegram messages on cron failures, gateway crashes, and restarts after a crash. Events are also appended to ~/.malda/gateway-alerts.log. malda doctor reports gateway state and previous crashes via ~/.malda/gateway-crash.json.
32.2.5 malda cron
Manage scheduled jobs stored in ~/.malda/cron.json. Jobs can run via the gateway scheduler, malda cron install (Windows Task Scheduler), or your system cron.
malda cron add --name <name> --message <message> --cron <cron-expr> [--scope <scope>]— Adds a job. Default scope iscron:<name>so scheduled turns use isolated GraphMemory facts.malda cron list— Lists all jobs (id, name, scope, message, cron).malda cron remove <job-id>— Removes the job with the given id.malda cron install— On Windows, syncs jobs to Task Scheduler withMALDA_MEMORY_SCOPEset per job.
32.3 Config file (~/.malda/config.json)
Optional. The assistant and some built-ins read config from the current directory first (./.malda/config.json), then from the user directory (~/.malda/config.json).
Minimal structure (OpenRouter only):
{
"providers": { "openrouter": { "apiKey": "sk-..." } },
"agents": { "defaults": { "model": "anthropic/claude-sonnet" } },
"tools": { "web": { "search": { "apiKey": "BSA-..." } } }
}
providers.openrouter.apiKey— Used by the default assistant whenOPENROUTER_API_KEYis not set.agents.defaults.model— Default LLM model for the assistant (e.g.anthropic/claude-sonnet).tools.web.search.apiKey— Brave Search API key so the assistant can use the web search tool when you add it.
In MALDA scripts, use getMaldaConfig() to read this config as an object (or null if the file is missing). See Built-in Functions for getMaldaHome() and getMaldaConfig().
32.3.1 agents.memory schema
The assistant reads agents.memory for GraphMemory behavior (embedding, reflection, KB indexing, and retention).
{
"agents": {
"memory": {
"embed": "hash",
"modelPath": "",
"pruneEpisodicAfterDays": 30,
"consolidateMinEpisodic": 3,
"maxNodes": 5000,
"reflectEnabled": false,
"reflectMinEpisodic": 3,
"reflectEveryNSaves": 1,
"reflectModel": "",
"reflectMinConfidence": 0.7,
"kbDir": "",
"kbPattern": "**/*.md",
"scopeParent": "project:myapp",
"scopeHierarchy": ["project:myapp", "org:acme", "global"],
"rerankMode": "onnx",
"rerankModelPath": "~/.malda/models/cross-encoder"
}
}
}
scopeParent- Single parent scope in the memory hierarchy (e.g.chat:123→project:myapp→global). Applied viaagent.setMemoryScopeParent()whenscopeHierarchyis not set.scopeHierarchy- Multi-level scopes between the active scope andglobal, e.g.["project:myapp", "org:acme", "global"]. The assistant prepends the active scope (chat:{id}on Telegram) at runtime. OverridesscopeParent. Env alternative:MALDA_MEMORY_SCOPE_HIERARCHY(comma-separated or JSON array).rerankEnabled,rerankMode,rerankModelPath,rerankTopK- Applied to everythink()memory query viaagent.setMemoryRerank(). UsererankMode: onnxwith a directory containingmodel.onnxandvocab.txt, orcrossfor local heuristic rerank.embed-hash(default),bow, orllama.modelPath- Embedding model path used whenembed = "llama".pruneEpisodicAfterDays,consolidateMinEpisodic,maxNodes- Maintenance limits for episodic retention and memory growth.reflectEnabled,reflectMinEpisodic,reflectEveryNSaves,reflectModel,reflectMinConfidence- Reflection schedule and quality controls.kbDir,kbPattern- Optional knowledge-base directory/pattern reindexed withreindexDocuments(..., { changedOnly: true }).MALDA_MEMORY_REFLECT=1forces reflection mode;MALDA_MEMORY_REFLECT_MIN_CONFIDENCEoverridesreflectMinConfidence.
32.4 Selecting OpenRouter vs local llama.cpp
The default assistant can use either OpenRouter (remote) or a local llama.cpp-based model. The backend is controlled by agents.defaults.backend in config.json and can be overridden per run with MALDA_AGENT_BACKEND or a CLI flag.
Extended structure:
{
"providers": {
"openrouter": {
"apiKey": "sk-...",
"model": "anthropic/claude-sonnet"
},
"local_llama": {
"modelPath": "C:/Users/YourName/AppData/Local/MaldaLang/Models/default/qwen2.5-0.5b-instruct-q4_k_m.gguf",
"contextLength": 4096,
"gpuLayers": 0,
"temperature": 0.7,
"maxTokens": 2000
}
},
"agents": {
"defaults": {
"backend": "openrouter", // or "local-llama"
"model": "anthropic/claude-sonnet"
}
},
"tools": { "web": { "search": { "apiKey": "BSA-..." } } }
}
agents.defaults.backend—"openrouter"(default) or"local-llama". Controls which LLM client the default assistant uses.providers.openrouter.model— Optional override for the OpenRouter model name. If set, it wins overagents.defaults.modelfor the default assistant.providers.local_llama.modelPath— Path to a local GGUF model file used byLlamaCppClient. Required whenbackend = "local-llama".providers.local_llama.contextLength— Optional context window size passed toLlamaCppClient.providers.local_llama.temperature,maxTokens,gpuLayers— Optional tuning parameters applied viasetTemperature(),setMaxTokens(), andsetGpuLayerCount()on the local client.MALDA_AGENT_BACKEND— Environment variable that overridesagents.defaults.backendfor the current process (e.g.MALDA_AGENT_BACKEND=local-llama).
32.5 Default assistant behavior
The default assistant script (e.g. Examples/Assistant/assistant.malda) does the following:
- Reads API key and default model from
getMaldaConfig()or fromOPENROUTER_API_KEY. - Creates an
OpenRouterClientand anAgentwith a fixed system prompt. - Adds the web search tool (
createWebSearchTool()) if a Brave Search API key is present in config or inBRAVE_SEARCH_API_KEY. - Creates a
GraphMemorywithembedHash(384 dimensions) by default, orembedBagOfWords/LlamaEmbedderwhen configured. Initializes beforeload()so the embedding function survives reload. Loads from~/.malda/memory/assistantwhen present, attaches memory withagent.useMemory(memory), and saves after each turn. - Embedding mode:
MALDA_MEMORY_EMBEDorconfig.agents.memory.embed—hash(default),bow, orllama(requiresconfig.agents.memory.modelPathorproviders.local_llama.embedModelPath). - Memory scope: Telegram sets
MALDA_CHAT_IDper message; the agent scopes reads/writes tochat:{id}. Override withMALDA_MEMORY_SCOPEoragent.setMemoryScope(). SetMALDA_MEMORY_SCOPE_PARENT=project:fooso queries inherit project-level facts viascopeHierarchy. - Memory maintenance on each turn:
consolidate()orreflect()(whenreflectEnabled/MALDA_MEMORY_REFLECT=1),prune()of old consolidated episodics, andenforceLimits()(default 5000 nodes). WithreflectAsync(default on viaMALDA_MEMORY_REFLECT_ASYNC),reflectAsync()runs aftersave()so the response is not blocked. - Rotating backups on
save()whenMALDA_MEMORY_BACKUP=trueorconfig.agents.memory.backupEnabled(default 5 snapshots viamaxBackups). - Health check:
memory.validate()ormalda memory validate. - When
config.agents.memory.kbDiris set, the assistant runsreindexDocumentson each save and startsmemory.startKbWatch()at launch (disable withMALDA_MEMORY_KB_WATCH=false). - Scripts can use
getAssistantMemory()instead of manualGraphMemorysetup for the default~/.malda/memory/assistantpath (hash/bow embed viaMALDA_MEMORY_EMBED). - Agent retrieval uses
hybridLexicalalongside vector search and synapse re-ranking for better matches on names, paths, and IDs. - If
MALDA_AGENT_MESSAGEis set (e.g. bymalda agent -m "..."), runs one shot and prints the response; otherwise runs an interactive loop.
You can override the script by setting MALDA_AGENT_SCRIPT to the path of your own .malda file or by placing assistant.malda in ~/.malda/.
32.6 Scheduling on Windows (Task Scheduler)
malda cron add/list/remove store job definitions in %USERPROFILE%\.malda\cron.json. MALDA does not run them itself, but on Windows you can either use malda cron install to create tasks automatically, or configure Task Scheduler manually.
32.5.0 Automatic installation (malda cron install)
To sync all jobs from %USERPROFILE%\.malda\cron.json into Windows Task Scheduler, run:
malda cron install
This command:
- Reads all jobs from
%USERPROFILE%\.malda\cron.json. - Removes any existing tasks whose names start with
MALDA_cron_(previous installs). - Creates a Task Scheduler task for each job using
schtasks, with the appropriate daily or weekday trigger.
Only a small subset of cron expressions is supported for automatic mapping:
| Cron expression | Meaning | Task Scheduler trigger |
|---|---|---|
0 9 * * * | 9:00 AM every day | /SC DAILY /ST 09:00 |
0 18 * * * | 6:00 PM every day | /SC DAILY /ST 18:00 |
0 9 * * 1-5 | 9:00 AM Mon–Fri | /SC WEEKLY /D MON,TUE,WED,THU,FRI /ST 09:00 |
The in-process malda gateway scheduler also supports */N minute intervals, comma-separated hours (0 9,18 * * *), monthly (0 9 1 * *), and multi-weekday (0 9 * * 1,3,5) expressions.
32.5.1 Add the job in MALDA
Record the message and schedule so you can reuse the message when creating the task:
malda cron add --name "daily" --message "Good morning! What's on my calendar today?" --cron "0 9 * * *"
Note the job id and the exact message; you will use the same message in the scheduled task.
32.5.2 Find malda.exe
Task Scheduler needs the full path to the MALDA executable. If you run from the repo with dotnet run, the executable is under the project output, e.g.:
MaldaLang\bin\Debug\net8.0\malda.exeorMaldaLang\bin\Release\net8.0\malda.exe(relative to the solution root).
From PowerShell you can run where.exe malda if malda is on your PATH; otherwise use the path above. Use this full path as the program in the task.
32.5.3 Create the scheduled task (GUI)
- Press Win + R, type
taskschd.msc, press Enter. - Click Create Task (not “Create Basic Task” so you can set a daily trigger at a specific time).
- General tab: name the task (e.g. “MALDA daily assistant”). Choose “Run whether user is logged on or not” or “Run only when user is logged on” as needed.
- Triggers tab → New: set Daily and the time (e.g. 9:00 AM for cron
0 9 * * *). - Actions tab → New:
- Program/script: full path to
malda.exe(e.g.C:\Users\You\Documents\maldalang\MaldaLang\bin\Debug\net8.0\malda.exe). - Add arguments:
agent -m "Good morning! What's on my calendar today?"(use the exact message frommalda cron add; escape or use single quotes if the message contains double quotes).
- Program/script: full path to
- Start in (optional): set to the folder where MALDA can find the assistant script (e.g. the repo root so
Examples/Assistant/assistant.maldais found). - Click OK to save. Create one task per scheduled message (e.g. one for 9:00, one for 18:00).
32.5.4 Create the scheduled task (command line)
Using schtasks to create a daily task at 9:00 AM:
schtasks /Create /TN "MALDA daily" /TR "\"C:\Path\To\malda.exe\" agent -m \"Good morning! What's on my calendar today?\"" /SC DAILY /ST 09:00 /RU "%USERNAME%"
Replace C:\Path\To\malda.exe with your actual malda.exe path and the message with the one you stored in malda cron add. /RU %USERNAME% runs the task as your user so it can read %USERPROFILE%\.malda and your config.
32.5.5 Environment and API key
The task runs in a clean environment. The assistant needs either:
- Config file: run
malda onboardand putproviders.openrouter.apiKeyin%USERPROFILE%\.malda\config.json(no environment variable needed for the task), or - Environment variable: set
OPENROUTER_API_KEYin your user or system environment variables (System Properties → Environment variables) so the task sees it when run as your user.
32.5.6 Mapping cron expressions to Task Scheduler
MALDA stores cron expressions in %USERPROFILE%\.malda\cron.json and does not run them directly. You can either let malda cron install map supported patterns to Task Scheduler triggers automatically (see above), or translate them manually when creating tasks yourself:
| Cron expression | Meaning | Task Scheduler |
|---|---|---|
0 9 * * * | 9:00 AM every day | Trigger: Daily, 9:00 AM |
0 18 * * * | 6:00 PM every day | Trigger: Daily, 18:00 |
0 9 * * 1-5 | 9:00 AM Mon–Fri | Trigger: Daily, 9:00 AM, repeat weekly Mon–Fri (or use “Weekdays”) |
Use malda cron add to define and list jobs, then either run malda cron install (Windows) or configure your system scheduler to run malda agent -m "message" at the matching times.
32.7 Skills
Skills are MALDA files in ~/.malda/skills/ that export tools (and optionally an agent) for the assistant. You can load them in two ways.
Static import
Use using Alias = skills.skillname to load ~/.malda/skills/skillname.malda and import its globals under the alias. For example:
using GithubSkill = skills.github;
// Then: GithubSkill.tools, GithubSkill.agent (if the skill exports them)
Dynamic loading
Use loadSkillsFromDir() to scan ~/.malda/skills/*.malda in one call, or getSkillNames() + loadSkill(name) for explicit control. Each loaded skill is an object with module globals plus a name field; failed loads include an error string. The default assistant uses loadSkillsFromDir(), adds each skill’s tools array to the agent, and registers each skill’s agent via addSubAgent when present.
var skills = loadSkillsFromDir();
for (var i = 0; i < skills.length; i++) {
var s = skills[i];
if (s.error != null && s.error != "") continue;
if (s.tools != null) {
for (var j = 0; j < s.tools.length; j++) agent.addTool(s.tools[j]);
}
if (s.agent != null) {
var desc = s.agentDescription != null && s.agentDescription != ""
? s.agentDescription : "Delegates to the " + s.name + " skill specialist.";
agent.addSubAgent(s.agent, desc);
}
}
Skill file convention
A skill file should export at least a tools array. Optionally export agent (an Agent instance) and agentDescription (tool description shown to the orchestrator). malda onboard installs a working template at ~/.malda/skills/greeting.malda (tool + sub-agent). Example:
// ~/.malda/skills/greeting.malda (installed by malda onboard)
@Tool("greet_user", "Greets someone by name", "...")
function greetUserTool(args) { ... }
var tools = ["greet_user"];
var agentDescription = "Greets users by name.";
var agent = new Agent("GreetingSkill", "specialist", "...", skillClient);
agent.addTool("greet_user");
Place additional .malda files in ~/.malda/skills/. The assistant discovers and loads them automatically when using the default script. Run malda doctor to validate skill syntax.
32.8 Channels / Telegram
You can run the assistant over a channel so it communicates with users via an external transport. The same assistant script is used; the host injects a channel that supplies input (e.g. from Telegram) and sends print() output back to that channel.
Running over Telegram
To run the assistant as a Telegram bot:
- Create a bot with BotFather and obtain the bot token.
- Set the token via
TELEGRAM_BOT_TOKENor in~/.malda/config.jsonunderchannels.telegram.botToken. - Run
malda agent -c telegramormalda agent --channel telegram.
The process stays running and uses long polling to receive messages. Each message you send to the bot is passed to the assistant as input(); the assistant’s reply (from print()) is sent back to the same chat. The same script (assistant.malda) and config (API keys, model, tools, memory) are used as for the console; only the source of input and the destination of output change.
Config example
{
"providers": { "openrouter": { "apiKey": "sk-..." } },
"agents": { "defaults": { "model": "anthropic/claude-sonnet" } },
"channels": { "telegram": { "botToken": "123456:ABC-DEF..." } }
}
If the token is missing when you run malda agent -c telegram or malda gateway, the CLI prints an error and exits. Each Telegram chat gets its own memory scope (chat:{id}); global memories (no scope) remain visible in every chat. For a persistent bot with scheduled jobs, prefer malda gateway over malda agent -c telegram.
See Also
- 13. Built-in Functions —
getMaldaHome(),getMaldaConfig(),ensureDir(),webSearch() - Agent Orchestration — Agents, tools, and GraphMemory
- Appendix — Command-line reference summary
- 32.7 above — Channels / Telegram for
malda agent -c telegram