mlld
v2.1.0
Published
An LLM scripting language for orchestrating workflows and building agents with infrastructure-level defense against prompt injection
Maintainers
Readme
mlld
A scripting language for secure LLM orchestration. Compose prompts from files, commands, and modules. Chain LLM calls with pipelines. Label sensitive data and define guards to control where it flows.
npm install -g mlld
mlld quickstart # give this to your llm
mlld skill install # install skills for coding agentsVSCode Extension | Documentation
Quick Example
var @commits = run cmd {git log --since="yesterday" --oneline}
var @prs = run cmd {gh pr list --json title,url}
exe @claude(prompt) = run cmd {claude -p "@prompt"}
var @summary = @claude(`
Summarize my work from yesterday:
Commits: @commits
PRs: @prs
`)
show @summaryWhy mlld?
- Security - Labels track data provenance, policies control what can happen, guards enforce at runtime
- Context composition - Assemble prompts from files, commands, and modules
- Orchestration - Create sophisticated resumable parallel orchestration loops
- Agents - Build auditable, securable agents with far less code
- Modules - Share and version workflows and security policies like npm packages
Core Syntax
var @name = "Alice" >> Variables
var @config = <config.json> >> Load files with angle brackets
var @output = run cmd {echo "hello"} >> Capture command output
show `Hello @name!` >> Output with interpolation
log `Debug: @config.version` >> Log to stderr
exe @greet(name) = `Hello @name!` >> Define reusable functions
exe @process(data) = js { return data.map(x => x * 2) }
exe @fetch(url) = run cmd {curl -s "@url"}Imports
import { @helper, @util } from @alice/tools
import { @config } from "./local-file.mld"
import @company/prompts as @prompts
>> Import types control caching behavior
import module { @api } from @corp/sdk >> Offline after install
import static { @template } from "./prompt.md" >> Embedded at parse time
import live <https://api.io/status> as @status >> Fresh every execution
import cached(1h) <https://feed.xml> as @feed >> Cached with TTLConditionals
>> Imperative branching
if @isProd [
show "Production mode"
] else [
show "Local mode"
]
>> Pattern matching (first match wins)
exe @grade(score) = when [
@score >= 90 => "A"
@score >= 80 => "B"
* => "C"
]
show `Grade: @grade(91)` >> A
>> Simple condition
when @verbose => log "Verbose mode"
>> Retry until valid
exe @validate(input) = when [
@input.valid => @input
@mx.try < 3 => retry "needs more detail"
* => "fallback"
]
>> Switch-style
when [
@role == "admin" => show "Admin panel"
@role == "user" => show "Dashboard"
* => show "Login required"
]Iteration
var @files = ["a.md", "b.md", "c.md"]
>> Execute for each
for @file in @files => show `Processing @file`
>> Collect results
var @sizes = for @file in @files => run cmd {wc -c @file}
>> Parallel execution
exe @review(file) = run cmd {claude -p "Review @file"}
var @sources = <src/**/*.ts>
for parallel @f in @sources => @review(@f)
>> Transform each item
exe @upper(s) = js { return s.toUpperCase() }
var @caps = for @f in @files => @upper(@f)
>> Loop with done/continue control
var @result = loop(10) [
let @count = (@input ?? 0) + 1
when @count >= 3 => done @count
continue @count
]
>> Loop with until clause and pacing
loop(endless, 10ms) until @input >= 3 [
let @next = (@input ?? 0) + 1
continue @next
]
>> While pipeline — the stage must signal `done` to terminate
exe @decrement(n) = when [
@n <= 0 => done @n
* => @n - 1
]
var @countdown = 3 | while(100) @decrementPipelines
exe @transform(data) = js { return data.items }
exe @validate(items) = js { return items.filter(n => n > 1) }
var @result = run cmd {cat data.json} | @parse | @transform | @validate
>> Built-in transformers (bare @input is the stage input, so it needs an exe body)
exe @parseJson(raw) = @raw | @parse >> Parse JSON (loose by default)
exe @parseStrict(raw) = @raw | @parse.strict >> Strict JSON only
exe @parseLlm(raw) = @raw | @parse.llm >> Extract JSON from an LLM response
>> Parallel stages
exe @fetchA() = "A"
exe @fetchB() = "B"
exe @fetchC() = "C"
var @results = || @fetchA() || @fetchB() || @fetchC()File Loading
var @readme = <README.md> >> Load file contents
var @config = <config.json> >> JSON auto-parsed
var @docs = <docs/*.md> >> Glob patterns
var @optional = <missing.md>? >> Null if missing
>> Field access
var @version = <package.json>.version
var @title = <post.md>.mx.fm.title >> Frontmatter access
>> AST selectors (extract code definitions)
var @func = <src/api.ts { createUser }> >> Specific function
var @handlers = <src/*.ts { handle* }> >> Wildcard pattern
var @allFuncs = <src/util.ts { *fn }> >> All functions
var @names = <src/api.ts { ?? }> >> List all definition namesBuiltin Methods
var @list = ["apple", "banana", "cherry"]
var @text = "Hello World"
show @list.includes("banana") >> true
show @list.join(", ") >> "apple, banana, cherry"
show @text.toLowerCase() >> "hello world"
show @text.split(" ") >> ["Hello", "World"]
>> Template interpolation shortcuts
show `Hello @name??"stranger"!` >> Nullish fallback
show `Items: @count?` >> Omit if falsy
>> Type checking
when @data.isArray() => show "It's an array"
when @value.isDefined() => show "Value exists"Input
Pass data to scripts via CLI flags or SDK. Passed-in input lives under the channel-keyed @input namespace — @input.cli for CLI flags, @input.sdk for SDK calls — with no import required:
show `Topic: @input.cli.topic, Count: @input.cli.count`
>> Import for destructuring
import { @topic, @count } from @inputmlld run myscript --topic foo --count 5
mlld script.mld --topic foo --count 5Security and Prompt Injection Defense
mlld accepts that LLMs can be tricked by prompt injection. Instead of trying to make LLMs resistant to attacks, it gives you the tools to prevent the consequences from manifesting. Every operation the LLM attempts passes through an execution layer the LLM cannot circumvent. That layer enforces the policies and guards you declare — it is where your rules are applied, not a set of defaults that protect an unconfigured script.
Labels and Taint Propagation
Every value carries metadata about what it is and where it came from. Labels propagate through all transformations — templates, pipelines, method chains, field access. No bypass through encoding or splitting.
var secret @apiKey = "sk-12345"
var pii @email = "[email protected]"
var untrusted @userInput = <form-data.txt>Source labels are applied automatically — MCP tool outputs carry src:mcp, command outputs carry src:cmd, file content carries src:file. An attacker who base64-encodes a secret, splits it into chunks, and tries to exfiltrate each chunk finds that every chunk still carries the secret label. Labels track identity through transformations, not content patterns.
Policies
Policies declare what should and shouldn't happen through classification:
import { @noSecretExfil, @noSensitiveExfil, @noUntrustedDestructive, @noUntrustedPrivileged } from "@mlld/policy"
var @policyConfig = {
labeling: { unlabeled: "untrusted" },
defaults: {
autosign: ["instructions"],
autoverify: true
},
labels: {
alias: {
"risk:exfil": ["op:net:w"],
"risk:destructive": ["op:fs:w"]
}
},
capabilities: {
allow: ["cmd:git:*", "cmd:npm:*"],
deny: ["sh"]
}
}
policy @p = union(@noSecretExfil, @noSensitiveExfil, @noUntrustedDestructive, @noUntrustedPrivileged, @policyConfig)Imported policy fragments block dangerous label-to-operation flows. labels.alias maps risk categories to the operation labels that count as that risk — policy classifies labeled functions by what they do. capabilities controls what operations can run at all: under the policy above, an LLM tricked into attempting rm -rf / is blocked by the capability check, because rm is not in allow.
Enforcement is opt-in. With no active policy, mlld runs commands exactly as your shell would — the same rm -rf deletes the file. Declaring a policy is what turns the capability check on. See known gaps for what this build does and does not enforce.
Guards
Guards manage the flow of data and can transform/sanitize:
>> Block secrets from shell and display
guard before secret = when [
@mx.op.type == "run" => deny "Secrets cannot be passed to shell"
@mx.op.type == "show" => deny "Secrets cannot be displayed"
* => allow
]
>> Transform instead of blocking — redact for display
guard before secret = when [
@mx.op.type == "show" => allow `@input.slice(0, 4)****`
* => allow
]
>> Validate and retry LLM responses
guard after llmjson = when [
@isValidJson(@output) => allow
@mx.guard.try < 3 => retry "Response must be valid JSON"
* => deny "Invalid JSON after 3 attempts"
]Privileged guards can remove protected labels — non-privileged guards cannot:
guard privileged @validateMcp after src:mcp = when [
@schema.valid(@output) => trusted! @output
* => deny "Invalid schema"
]Trust is asymmetric — anyone can mark data as untrusted, but only privileged guards can bless data as trusted (trusted!), remove labels (!label), or clear all labels (clear!). This mirrors real-world security: raising a concern is easy, clearing one requires authority.
Sealed Credentials
Credentials flow from the OS keychain or the environment straight into the command's environment variables through paths declared in policy, never becoming interpolatable values:
policy @p = {
capabilities: { allow: ["cmd:claude:*"] },
credentials: { claude: { from: "env:ANTHROPIC_API_KEY", as: "ANTHROPIC_API_KEY" } }
}
run cmd { claude -p "Summarize this repo" } using creds:claude with { policy: @p }The secret is declared in policy.credentials — from: "env:NAME" or from: "keychain" — and injected as an env var at execution time. There is no @apiKey variable for the LLM to reference or exfiltrate.
Boxes
Scoped execution with a declared filesystem view, credentials, and capability control. Writes inside a box land in the box's own filesystem, not on yours:
policy @p = {
capabilities: {
allow: ["cmd:cat:*", "fs:w"],
network: { allow: [] },
deny: ["mcp:*"]
}
}
var @sandbox = {
fs: { read: [".:/app"], write: ["/tmp"] },
tools: ["Read", "Bash"]
}
box @sandbox [
file "task.md" = "Analyze the codebase"
run cmd { cat task.md }
]box {} is a no-op container. It does not implicitly create a workspace bridge or default VFS tools unless you explicitly request workspace, tool, or MCP config.
Child environments can only restrict their parent's capabilities, never extend them. Code that runs inside the box is limited to the filesystem, network, and capabilities the box and its policy declare.
OS-level isolation providers (@mlld/env-docker, @mlld/env-sprites) are specified but not shipped in this release — box blocks run with the local provider, so what a box enforces today is mlld's own capability and filesystem checks, not an operating-system sandbox. See known gaps.
Signing and Verification
Sign templates at authoring time to create a trust boundary between authentic instructions and untrusted data. The LLM verifies signatures at runtime and can distinguish developer-written instructions from injected content.
Audit Ledger
Every security-relevant event is recorded in .llm/sec/audit.jsonl — signing, verification, label changes, and file write taint. When labeled data is written to disk and read back, the audit ledger restores those labels, closing what would otherwise be an obvious evasion path.
Deny Handlers
Handle blocked operations gracefully instead of crashing:
guard before secret = when [
@mx.op.type == "exe" => deny "Secrets cannot be passed to shell"
* => allow
]
exe @runProcess(input) = run cmd {echo "@input"}
exe @process(input) = when [
denied => `Blocked: @mx.guard.reason`
* => @runProcess(@input)
]
var secret @key = "abc"
show @process(@key) >> Blocked: Secrets cannot be passed to shellHooks
User-defined before/after hooks on functions, operations, and data labels for observation and extensibility:
hook before op:named:myfunc = [
log `Calling @mx.hook.target`
]
hook after op:for:iteration = [
log `Iteration @mx.for.index of @mx.for.total`
]Modules
Using Modules
import { @helper } from @alice/utils
import { @helper } from @alice/[email protected] >> Specific version
import { @helper } from @alice/utils@beta >> Tagged versionCreating Modules
---
name: my-utils
author: alice
version: 1.0.0
about: Utility functions
needs: [js]
license: MIT
---
exe @double(n) = js { return n * 2 }
exe @greet(name) = `Hello @name!`
export { @double, @greet }Module Commands
mlld install @alice/utils # Install from registry
mlld update @alice/utils # Update to latest
mlld outdated # Check for updates
mlld ls # List installed modules
mlld module # Create new module
mlld publish module.mld.md # Publish to registry
mlld publish --tag beta # Publish with tag
mlld publish --private # Publish to private repoMCP Server
Expose mlld functions as MCP tools:
mlld mcp # Serve functions from llm/mcp/
mlld mcp tools.mld # Use specific moduleLive RPC
Start the persistent NDJSON RPC transport over stdio:
mlld live --stdioClaude Code Plugin
Install the mlld plugin for Claude Code to get orchestrator authoring skills, starter scaffolding, and language server integration.
mlld plugin installIncludes:
- Orchestrator skill — patterns for audit, research, and development pipelines
- Agent skill — tool agents, event-driven agents, workflow agents
/mlld:scaffoldcommand — generate starter orchestrator projects from templates- Language server —
.mldsyntax highlighting and diagnostics - MCP dev tools — development-time tooling via
mlld mcp-dev
CLI Reference
# Running files
mlld file.mld # Run a file (output goes to stdout)
mlld file.mld --watch # Auto-rerun on changes
mlld file.mld --mlld-env .env.local # Load agent env file
mlld file.mld --flag value # Pass payload flags
# Scripts (from llm/run/ by default)
mlld run # List available scripts
mlld run deploy # Run deploy.mld
mlld run deploy --resume # Resume from last checkpoint
mlld run deploy --fresh # Ignore checkpoints
mlld run deploy --fork other # Seed cache from another script's checkpoints
# Checkpoints
mlld checkpoint list <script> # View checkpoints
mlld checkpoint inspect <script> # Inspect checkpoint details
mlld checkpoint clean <script> # Clear a script's checkpoint cache
# Project setup
mlld init # Quick project setup
mlld module # Create new module
mlld validate <file|dir> # Check syntax and catch mistakes
mlld validate <file|dir> --error-on-warnings # Exit 1 on warnings
mlld howto # Browse documentation topics
# Configuration
mlld setup # Interactive setup wizard
mlld alias --name lib --path ./src/lib
# CI/CD (ephemeral execution)
npx mlldx script.mld # No filesystem persistence