ACL Engine is a lightweight Python execution engine for AI-generated action scripts.
It parses a compact ACL syntax, validates each command, applies safety checks, executes allowed actions in order, and returns structured JSON output.
The repository also includes syntax.md, SPEC.md, and runnable ACL samples in examples/.
pip install acl-enginePyPI installs include the Python package and the acl CLI.
If you are working from a repository checkout or source distribution, you also get:
syntax.mdfor a focused language guideSPEC.mdfor the formal execution contract- runnable ACL examples in
examples/
For local development:
pip install -e ".[dev]"ACL Engine supports:
- multi-line ACL scripts
- metadata like
@reason="debug" - variable assignment with
-> name - structured object and list values
- field and index selectors like
result.stdoutanditems[0] - sequential multi-step workflows
- safe built-in actions for files, system commands, network requests, memory, utility helpers, and data transformation
- JSON-light API and file workflows so models do not have to handcraft raw JSON for common integrations
- custom action registration from Python
- structured JSON results for both Python and CLI usage
- policy-driven execution modes for safer deployments
ACL intentionally does not support loops, conditionals, or nested control flow.
@reason="basic variable workflow"
util.echo(value="Hello from ACL Engine") -> greeting
memory.store(key="message", value=greeting)
memory.get(key="message") -> result
Structured values and selectors are supported:
util.echo(value={message="hello", items=["a", "b"]}) -> payload
util.echo(value=payload.message) -> msg
util.echo(value=payload.items[1]) -> second
Integration-oriented workflows are supported without dropping into Python:
net.request(method="GET", url="https://example.com/api/user") -> response
data.select(value=response.json, path="user.id") -> user_id
data.template(template="user-{id}", values={id=user_id}) -> label
Structured JSON files are also first-class, so ACL can move data between APIs and files without manual serialization steps:
data.pick(value=response.json, keys=["user", "meta"]) -> payload
file.write_json(path="cache/user.json", value=payload)
file.read_json(path="cache/user.json") -> stored
from acl_engine import execute_acl
result = execute_acl(
'''
util.echo(value="hello") -> msg
util.echo(value=msg) -> copy
'''
)
print(result)You can also create a reusable engine instance:
from acl_engine import ACLEngine
engine = ACLEngine(workspace="./workspace", timeout=30)
result = engine.execute('file.read(path="main.py") -> code')Run an .acl file from the CLI:
acl --workspace ./workspace run path/to/workflow.aclIf you want to invoke the CLI through Python directly:
python -m cli.main --workspace ./workspace run path/to/workflow.aclRun an .acl file from Python code:
from pathlib import Path
from acl_engine import ACLEngine
engine = ACLEngine(workspace="./workspace", timeout=30)
acl_text = Path("path/to/workflow.acl").read_text(encoding="utf-8")
result = engine.execute(acl_text)
print(result)For quick experiments without a file, you can still run inline ACL:
acl --workspace ./workspace exec 'util.echo(value="hello") -> msg'from acl_engine import ACLEngine
engine = ACLEngine()
def db_query(sql: str):
return {"sql": sql, "rows": []}
engine.register_action(
"db.query",
db_query,
{"sql": {"type": str, "min_length": 1}},
capabilities={"data"},
description="Run a SQL query",
returns="object",
)
result = engine.execute('db.query(sql="SELECT 1") -> output')If you are using a repository checkout or source distribution, there are runnable ACL samples in examples/ for common tool-oriented workflows.
If you installed only the wheel from PyPI, use the inline CLI examples below or copy the ACL snippets from the repository documentation.
Run an ACL file:
acl --workspace ./workspace run examples/basic.acl
acl --workspace ./workspace run examples/file_roundtrip.acl
acl --workspace ./workspace run examples/json_pipeline.aclRun with a stricter policy:
acl --workspace ./workspace --policy strict run examples/basic.aclRun inline ACL:
acl --workspace ./workspace exec 'util.echo(value="hello") -> msg'Validate ACL without executing it:
acl validate --inline 'util.echo(value="hello") -> msg'Inspect the execution plan:
acl dry-run --inline 'util.echo(value={message="hello"}) -> payload'List available actions and their schemas:
acl actionsFilter available actions for integration discovery:
acl actions --namespace data
acl actions --capability filesystemLoad custom actions from one or more plugin files:
acl --plugin ./plugins/example.py actionsContinue after an error instead of stopping on the first failed step:
acl --continue-on-error exec 'util.echo(value="ok") -> a
system.run(command="python", args=["-c", "print(1)"])
util.echo(value="still-runs") -> b'Restrict execution to specific capability groups:
acl --allow-capability utility --allow-capability memory exec 'util.echo(value="hello") -> msg'Allow only specific network domains:
acl --allow-domain example.com exec 'net.request(method="GET", url="https://example.com") -> response'Use the dev policy only for trusted local workflows that need interpreter-backed process execution:
acl --workspace ./workspace run examples/system_ls.acl
acl --workspace ./workspace --policy dev run examples/dev_python_probe.aclCLI output is JSON:
{
"acl_version": "1.0",
"output_version": "1.2",
"trace_id": "...",
"status": "success",
"data": {
"msg": "hello"
},
"logs": [
{
"step": 1,
"line": 1,
"action": "util.echo",
"raw_params": {
"value": "hello"
},
"resolved_params": {
"value": "hello"
},
"status": "success",
"duration_ms": 0,
"meta": {},
"result": "hello"
}
],
"summary": {
"policy": "standard",
"steps_total": 1,
"steps_completed": 1,
"steps_failed": 0,
"duration_ms": 0
},
"error": null
}ACL Engine applies a small, strict safety model:
- file operations are restricted to the configured workspace root
- path traversal outside the workspace is blocked
standardonly allowslsinsystem.rundevallowspython,node, andlsfor local iterationrm,sudo,&&, and;are blocked- subprocess execution uses
shell=False - subprocess calls respect the configured timeout
standard is the recommended profile for AI-facing or hosted usage.
dev and custom plugins should be treated as trusted local-development features, not as a security sandbox for untrusted scripts.
Execution policy profiles are available:
standard— safer default profile for deployed or AI-facing usestrict— disables network access and file deletion, and reduces allowed system commandsdev— relaxed local profile that enables interpreter-driven tooling
Safety checks run after variable resolution so indirection cannot bypass restrictions.
file.read(path)file.read_json(path)file.write(path, content)file.write_json(path, value, indent=2, sort_keys=false)file.append(path, content)file.list(path)file.delete(path)file.exists(path)file.stat(path)file.mkdir(path)file.copy(source, destination, overwrite=false)file.move(source, destination, overwrite=false)file.glob(path=".", pattern="*")system.run(command, args=[], cwd=None)net.request(method, url, headers=None, query=None, body=None, json_body=None)memory.store(key, value)memory.get(key)memory.delete(key)memory.list()memory.clear()data.from_json(value)data.to_json(value, indent=2, sort_keys=false)data.merge(left, right, deep=false)data.template(template, values)data.select(value, path)data.pick(value, keys)data.omit(value, keys)util.echo(value)util.sleep(seconds)util.now()util.uuid()util.hash(value, algorithm="sha256")util.base64_encode(value)util.base64_decode(value)
acl_engine/
__init__.py
engine.py
parser.py
validator.py
executor.py
context.py
resolver.py
actions/
data.py
file.py
system.py
net.py
memory.py
util.py
registry.py
cli/
main.py
examples/
basic.acl
tests/
pytestThe project is intentionally small and focused. The current goal is a reliable Python package and CLI, not a general-purpose workflow language.