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
These are the kinds of request → action → receipt flows you can show publicly. The point is not just chat. The point is safe actions with proof.
Great demo because it is relatable and ambiguous. The assistant should ask who paid before writing anything.
This shows where ACL is strongest: risky write actions across systems with approval and a receipt.
Simple language, multi-app action. Very visual in a screen recording.
Shows ACL is not only for chat. It can also run in the background and save receipts/history.
You do not need to rebuild your app as a chatbot. Expose machine-usable capabilities (API, MCP, Zapier, CLI), then use ACL to define how an agent may use them safely.
1. Expose CapabilitiesWrap existing app actions as tools (API wrappers, MCP adapters, Zapier webhooks, or CLI commands). ACL does not replace your app API.
2. Add Safety RulesUse ACL to define allowed tools, checks, retries/fallbacks, and MUST gates so agent actions do not become ad hoc prompt glue.
3. Preview Before WritesFor risky actions (orders, refunds, money movement), preview first, ask for confirmation, then execute. The same contract can support both steps.
4. Keep ReceiptsEvery run emits a receipt so support, engineers, and users can inspect what happened. ACL is for safe AI actions, not opaque automation.
# App capability (via API / Zapier / MCP) exposed as a tool: # zapier.invoke(action="calendar.create_event", ...) AGENT ScheduleEventPreview OUT answer TOOLS zapier.invoke, llm.generate STEP preview = TOOL zapier.invoke( action="calendar.create_event", mode="preview", approved=false, title="Lunch with Ali", starts_at="2026-02-23T13:00:00") MUST preview.status == "preview" STEP answer = TOOL llm.generate( prompt="Using {preview.preview}, confirm what will happen and ask for approval.") MUST has(answer, "text") RESULT answer
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 |