# ACL Engine

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/`.

## Installation

```bash
pip install acl-engine
```

PyPI installs include the Python package and the `acl` CLI.

If you are working from a repository checkout or source distribution, you also get:

- `syntax.md` for a focused language guide
- `SPEC.md` for the formal execution contract
- runnable ACL examples in `examples/`

For local development:

```bash
pip install -e ".[dev]"
```

## What It Does

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.stdout` and `items[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.

## Example ACL

```acl
@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:

```acl
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:

```acl
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:

```acl
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
```

## Python Usage

```python
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:

```python
from acl_engine import ACLEngine

engine = ACLEngine(workspace="./workspace", timeout=30)
result = engine.execute('file.read(path="main.py") -> code')
```

## Running `.acl` Files

Run an `.acl` file from the CLI:

```bash
acl --workspace ./workspace run path/to/workflow.acl
```

If you want to invoke the CLI through Python directly:

```bash
python -m cli.main --workspace ./workspace run path/to/workflow.acl
```

Run an `.acl` file from Python code:

```python
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:

```bash
acl --workspace ./workspace exec 'util.echo(value="hello") -> msg'
```

## Custom Actions

```python
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')
```

## CLI Usage

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:

```bash
acl --workspace ./workspace run examples/basic.acl
acl --workspace ./workspace run examples/file_roundtrip.acl
acl --workspace ./workspace run examples/json_pipeline.acl
```

Run with a stricter policy:

```bash
acl --workspace ./workspace --policy strict run examples/basic.acl
```

Run inline ACL:

```bash
acl --workspace ./workspace exec 'util.echo(value="hello") -> msg'
```

Validate ACL without executing it:

```bash
acl validate --inline 'util.echo(value="hello") -> msg'
```

Inspect the execution plan:

```bash
acl dry-run --inline 'util.echo(value={message="hello"}) -> payload'
```

List available actions and their schemas:

```bash
acl actions
```

Filter available actions for integration discovery:

```bash
acl actions --namespace data
acl actions --capability filesystem
```

Load custom actions from one or more plugin files:

```bash
acl --plugin ./plugins/example.py actions
```

Continue after an error instead of stopping on the first failed step:

```bash
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:

```bash
acl --allow-capability utility --allow-capability memory exec 'util.echo(value="hello") -> msg'
```

Allow only specific network domains:

```bash
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:

```bash
acl --workspace ./workspace run examples/system_ls.acl
acl --workspace ./workspace --policy dev run examples/dev_python_probe.acl
```

CLI output is JSON:

```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
}
```

## Safety Rules

ACL Engine applies a small, strict safety model:

- file operations are restricted to the configured workspace root
- path traversal outside the workspace is blocked
- `standard` only allows `ls` in `system.run`
- `dev` allows `python`, `node`, and `ls` for local iteration
- `rm`, `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 use
- `strict` — disables network access and file deletion, and reduces allowed system commands
- `dev` — relaxed local profile that enables interpreter-driven tooling

Safety checks run after variable resolution so indirection cannot bypass restrictions.

## Built-in Actions

- `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)`

## Project Layout

```text
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/
```

## Running Tests

```bash
pytest
```

## Status

The project is intentionally small and focused. The current goal is a reliable Python package and CLI, not a general-purpose workflow language.
