ACL is a declarative language for AI agent workflows. Every run produces a receipt — what was called, what was returned, and whether the rules passed.
AGENT SplitExpense IN receipt_url OUT split TOOLS vision.describe, llm.generate # Read the receipt image STEP scan = TOOL vision.describe(url=receipt_url, prompt="List every item and price") # Calculate fair split STEP calc = TOOL llm.generate(prompt="Split these items fairly: {scan.description}") MUST calc.text CONTAINS "$" # enforce output format END
ACL files define agents. Each agent has steps that call tools. Results flow between steps via string interpolation.
AGENT & STEPDeclare inputs, outputs, and a sequence of tool calls. Each step names its result for later reference.
MUST & CHECKEvidence gating. MUST halts on failure. CHECK records the result but continues. Both are in the receipt.
IF / FOREACHReal control flow. Branch on conditions, iterate over lists. Not just a linear pipeline.
{step.field}String interpolation. Reference any previous step's output inside arguments. Nested fields work too.
AGENT PriceAlert IN ticker OUT action STEP price = TOOL http.get(url="https://api.example.com/price/{ticker}") MUST price.status == 200 IF price.json.usd > 100000 STEP notify = TOOL email.draft(to="team@co.com", subject="{ticker} above $100k") ELSE STEP log = TOOL llm.generate(prompt="Summarize {ticker} at ${price.json.usd}") END END
AGENT BatchTranslator IN languages OUT translations STEP langs = TOOL json.parse(text=languages) FOREACH lang IN langs.data STEP t = TOOL llm.generate( prompt="Translate 'hello' to {lang}") END END
AGENT Researcher IN question OUT answer STEP r = TOOL react.run( goal=question, tools="search.web,browser.fetch", max_steps="5") MUST r.answer != "" END
| Keyword | Purpose |
|---|---|
| AGENT ... END | Define an agent with steps |
| IN / OUT | Declare input and output variables |
| TOOLS | Restrict which tools the agent may use |
| STEP name = TOOL fn(args) | Call a tool, store result as name |
| MUST expr | Hard assertion — agent fails if false |
| CHECK expr | Soft check — recorded, execution continues |
| IF / ELSE / END | Conditional execution |
| FOREACH var IN expr ... END | Iterate over a list |
| TEMPLATE name(params) | Reusable agent fragment |
| ALLOW / LIMIT | Global policy: tool allowlist, time/call/retry limits |
| SCHEDULE | Cron-based automatic execution |
| REMOTE | Delegate to an agent on another server |
Used in MUST, CHECK, and IF:
| ==, != | Equality |
| >, <, >=, <= | Numeric comparison |
| CONTAINS | String/substring match |
Field access: step.field, step.nested.field, step.array[0]
STEP a = TOOL llm.generate(prompt="What is AI?") STEP b = TOOL llm.generate(prompt="Translate to French: {a.text}")
$ acl serve agents.acl --port 8080
$ curl -X POST localhost:8080/run/SplitExpense \
-d '{"vars": {"receipt_url": "https://..."}}'
Response is the full receipt. Set ACL_SERVE_API_KEY for auth.
No plugins needed. Ships with the binary.
| Tool | Returns |
|---|---|
| llm.generate | text, model, tokens, provider |
| vision.describe | description, model, provider |
| audio.transcribe | text, language, duration_s |
| search.web | hits[{title,url,snippet}], count |
| browser.fetch | url, title, text, word_count |
| http.get / http.request | status, text, json, url |
| react.run | answer, steps_taken, log |
| json.parse / json.stringify | data / text |
| csv.parse / csv.write | rows, columns / text |
| sql.query | rows, count |
| file.read / file.write | text, path / path, written |
| memory.set / memory.get | key, stored_at / key, value |
| memory.messages | session, messages, count |
| vector.upsert / vector.search | id, dimensions / results, count |
| code.run | stdout, stderr, exit_code |
| pdf.render / email.draft | path / message_id |
go install github.com/ranausmanai/acl/cmd/acl@latest
acl run myagent.acl acl run myagent.acl --var name="World"
acl serve agents.acl --port 8080
Set one environment variable. ACL auto-detects:
| ANTHROPIC_API_KEY | Claude (claude-sonnet-4-6) |
| OPENAI_API_KEY | GPT-4o |
| GROQ_API_KEY | Llama 3.3 70B |
| MISTRAL_API_KEY | Mistral Large |
| OLLAMA_HOST | Local Ollama |
| ACL_DB_URL | Database for sql.query |
| ACL_CODE_RUN_ENABLED=1 | Enable code.run |
| ACL_SERVE_API_KEY | Bearer token for serve |
| ACL_FILE_DIR | Sandbox for file tools |
| BRAVE_API_KEY | Brave Search |
| SERPER_API_KEY | Google Search |