Open Source · v0.1.0

The language for
auditable AI agents

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.

Open Playground GitHub
$ go install github.com/ranausmanai/acl/cmd/acl@latest
expense_splitter.acl
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

Language overview

ACL files define agents. Each agent has steps that call tools. Results flow between steps via string interpolation.

AGENT & STEP

Declare inputs, outputs, and a sequence of tool calls. Each step names its result for later reference.

MUST & CHECK

Evidence gating. MUST halts on failure. CHECK records the result but continues. Both are in the receipt.

IF / FOREACH

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

price_alert.acl
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
foreach.acl
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
react_agent.acl
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

Language reference

Keywords

KeywordPurpose
AGENT ... ENDDefine an agent with steps
IN / OUTDeclare input and output variables
TOOLSRestrict which tools the agent may use
STEP name = TOOL fn(args)Call a tool, store result as name
MUST exprHard assertion — agent fails if false
CHECK exprSoft check — recorded, execution continues
IF / ELSE / ENDConditional execution
FOREACH var IN expr ... ENDIterate over a list
TEMPLATE name(params)Reusable agent fragment
ALLOW / LIMITGlobal policy: tool allowlist, time/call/retry limits
SCHEDULECron-based automatic execution
REMOTEDelegate to an agent on another server

Expressions

Used in MUST, CHECK, and IF:

==, !=Equality
>, <, >=, <=Numeric comparison
CONTAINSString/substring match

Field access: step.field, step.nested.field, step.array[0]

Interpolation

STEP a = TOOL llm.generate(prompt="What is AI?")
STEP b = TOOL llm.generate(prompt="Translate to French: {a.text}")

Serving as API

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


Built-in tools

No plugins needed. Ships with the binary.

ToolReturns
llm.generatetext, model, tokens, provider
vision.describedescription, model, provider
audio.transcribetext, language, duration_s
search.webhits[{title,url,snippet}], count
browser.fetchurl, title, text, word_count
http.get / http.requeststatus, text, json, url
react.runanswer, steps_taken, log
json.parse / json.stringifydata / text
csv.parse / csv.writerows, columns / text
sql.queryrows, count
file.read / file.writetext, path / path, written
memory.set / memory.getkey, stored_at / key, value
memory.messagessession, messages, count
vector.upsert / vector.searchid, dimensions / results, count
code.runstdout, stderr, exit_code
pdf.render / email.draftpath / message_id

Getting started

Install

go install github.com/ranausmanai/acl/cmd/acl@latest

Run

acl run myagent.acl
acl run myagent.acl --var name="World"

Serve

acl serve agents.acl --port 8080

LLM providers

Set one environment variable. ACL auto-detects:

ANTHROPIC_API_KEYClaude (claude-sonnet-4-6)
OPENAI_API_KEYGPT-4o
GROQ_API_KEYLlama 3.3 70B
MISTRAL_API_KEYMistral Large
OLLAMA_HOSTLocal Ollama

Configuration

ACL_DB_URLDatabase for sql.query
ACL_CODE_RUN_ENABLED=1Enable code.run
ACL_SERVE_API_KEYBearer token for serve
ACL_FILE_DIRSandbox for file tools
BRAVE_API_KEYBrave Search
SERPER_API_KEYGoogle Search