# ACL — AI Command Language Specification

**Version:** 1.0
**Output Version:** 1.2
**Status:** Normative

This document is the formal specification for the ACL language and its execution model. Any conforming implementation in any language MUST follow the rules defined here.

---

## 1. Overview

ACL (AI Command Language) is a minimal, sequential command language designed for AI-generated action scripts. It provides:

- namespaced action calls with typed parameters
- variable assignment and reference
- metadata annotations
- structured JSON output

ACL intentionally omits loops, conditionals, nested execution, and general programming constructs.

---

## 2. Syntax Grammar

### 2.1. Notation

This section uses a PEG-like notation:

- `"literal"` — literal text
- `[...]` — character class
- `*` — zero or more
- `+` — one or more
- `?` — optional
- `/` — ordered choice
- `(...)` — grouping

### 2.2. Top-Level Structure

```peg
Script        <- (Blank / Comment / Metadata / Command)* EOF
Blank         <- Whitespace* Newline
Comment       <- '#' (!Newline .)* Newline?
Metadata      <- '@' Identifier InlineWS* '=' InlineWS* String InlineWS* (Newline / EOF)
Command       <- ActionName WS* ParamBlock InlineWS* Assignment? InlineWS* (Comment / Newline / EOF)
Assignment    <- '->' InlineWS* Identifier
```

### 2.3. Action Names

```peg
ActionName    <- Namespace '.' Name
Namespace     <- [a-z_] [a-z0-9_]*
Name          <- [a-z_] [a-z0-9_]*
```

Action names MUST be lowercase, contain exactly one dot separator, and have non-empty namespace and name parts.

### 2.4. Parameter Block

```peg
ParamBlock    <- '(' WS* ParamList? WS* ')'
ParamList     <- Param (WS* ',' WS* Param)*
Param         <- Identifier WS* '=' WS* Value
```

The parameter block MAY span multiple lines. Whitespace (including newlines) between tokens inside `(...)` is ignored.

### 2.5. Values

```peg
Value         <- TripleString / String / Number / Boolean / Null / List / Object / VariableRef
TripleString  <- '"""' TripleChar* '"""'
String        <- '"' StringChar* '"'
Number        <- '-'? Digit+ ('.' Digit+)?
Boolean       <- 'true' / 'false'
Null          <- 'null' / 'None'
List          <- '[' WS* (Value (WS* ',' WS* Value)*)? WS* ']'
Object        <- '{' WS* (ObjEntry (WS* ',' WS* ObjEntry)*)? WS* '}'
ObjEntry      <- ObjKey WS* (':' / '=') WS* Value
ObjKey        <- String / Identifier
VariableRef   <- Identifier Selector*
Selector      <- '.' Identifier / '[' SelectorIdx ']'
SelectorIdx   <- Integer / String / Identifier
```

### 2.6. String Escapes

Inside `"..."` strings, these escape sequences are recognized:

| Escape | Meaning |
| ------- | ------- |
| `\\` | literal backslash |
| `\"` | literal double quote |
| `\n` | newline |
| `\t` | tab |

Any other `\X` produces the literal character `X`.

Triple-quoted strings `"""..."""` support the same escape sequences and additionally preserve literal newlines.

### 2.7. Identifiers

```peg
Identifier    <- [a-zA-Z_] [a-zA-Z0-9_]*
```

### 2.8. Whitespace

```peg
WS            <- [ \t\r\n]
InlineWS      <- [ \t]
Newline       <- '\r'? '\n'
```

### 2.9. Metadata

Metadata lines start with `@` and attach key-value pairs to the next command. Metadata is always single-line. Multiple metadata lines before a command are merged. Metadata not followed by a command is a parse error.

```acl
@reason="debug step"
@priority="high"
file.read(path="main.py") -> code
```

---

## 3. Execution Semantics

### 3.1. Sequential Execution

Commands MUST be executed sequentially in the order they appear. A conforming engine MUST NOT reorder, parallelize, or skip commands (except when stopping on error).

### 3.2. Execution Pipeline

For each command, the engine MUST execute these steps in order:

1. **Validate** — confirm action exists, check required/supported params, validate types (variable references are allowed)
2. **Apply defaults** — fill in missing optional parameters from schema defaults
3. **Resolve variables** — replace all `VariableRef` values with concrete values from context
4. **Validate resolved** — type-check resolved values, enforce value constraints
5. **Safety check** — enforce policy rules on resolved values (path containment, command whitelist, capability restrictions)
6. **Dispatch** — call the action function with resolved parameters
7. **Assign** — if `-> name` is present, store the result in context under `name`
8. **Log** — append a structured log entry

### 3.3. Stop-on-Error

When `stop_on_error` is true (default), execution halts after the first error and returns immediately. When false, execution continues and the final status reflects whether any step failed.

### 3.4. Determinism

Given the same input, policy, and external state, execution MUST produce the same sequence of action calls. Engines MUST NOT introduce non-determinism in command ordering.

---

## 4. Variable Resolution

### 4.1. Context

Each execution creates an empty context. When a command uses `-> name`, the action result is stored under `name` in the context.

### 4.2. Variable References

A bare identifier in a value position is a variable reference: `value=code`. If the identifier is not a keyword (`true`, `false`, `null`, `None`), it resolves against the context.

### 4.3. Selectors

Variable references support chained selectors:

- `.field` — dictionary key or object attribute access
- `[index]` — integer index into a list
- `["key"]` — string key into a dictionary

Example: `result.items[0].name`

### 4.4. Resolution Failure

If a variable is not found in context, the engine MUST raise `ACLVariableError`. If a selector path fails (missing key, index out of range), the engine MUST raise `ACLVariableError`. Silent fallbacks or default values are forbidden.

### 4.5. Pre-execution Reference Check

During validation/dry-run, the engine SHOULD check that all variable references refer to variables that will have been assigned by preceding commands.

---

## 5. Type System

### 5.1. Parameter Types

Each action parameter declares accepted types from:

- `str` — string
- `int` — integer (booleans excluded)
- `float` — floating-point number
- `bool` — boolean
- `list` — ordered list of values
- `dict` — key-value mapping
- `object` — any type (wildcard)
- `None`/`NoneType` — null value

A parameter MAY accept multiple types: `(str, type(None))` means string or null.

### 5.2. Dual Validation

Type checking happens twice:

1. **Before resolution:** `VariableRef` values are allowed regardless of declared type
2. **After resolution:** all values MUST match declared types exactly

### 5.3. Value Constraints

Parameters MAY declare additional constraints:

| Constraint | Applies to | Meaning |
| --- | --- | --- |
| `min_length` | `str` | minimum string length |
| `max_length` | `str` | maximum string length |
| `pattern` | `str` | regex that must fully match the value |
| `min_value` | `int`, `float` | minimum numeric value |
| `max_value` | `int`, `float` | maximum numeric value |

Constraint validation MUST occur after resolution, on concrete values only.

---

## 6. Action System

### 6.1. Action Registration

Every action MUST have:

- a unique namespaced name (`namespace.action`)
- a callable implementation
- a parameter schema (types, required flags, defaults, constraints)
- zero or more capability tags

### 6.2. Capabilities

Each action declares a set of capability tags. Standard capabilities:

| Capability | Actions |
| --- | --- |
| `filesystem` | `file.read`, `file.read_json`, `file.write`, `file.write_json`, `file.append`, `file.list`, `file.delete`, `file.exists`, `file.stat`, `file.mkdir`, `file.copy`, `file.move`, `file.glob` |
| `process` | `system.run` |
| `network` | `net.request` |
| `memory` | `memory.store`, `memory.get`, `memory.delete`, `memory.list`, `memory.clear` |
| `data` | `data.from_json`, `data.to_json`, `data.merge`, `data.template`, `data.select`, `data.pick`, `data.omit` |
| `utility` | `util.echo`, `util.sleep`, `util.now`, `util.uuid`, `util.hash`, `util.base64_encode`, `util.base64_decode` |

Custom actions MUST declare their capabilities. The policy MAY restrict which capabilities are allowed.

### 6.3. Namespacing

Action names MUST match `^[a-z_][a-z0-9_]*\.[a-z_][a-z0-9_]*$`. Custom actions SHOULD use a unique namespace to avoid collisions.

### 6.4. Schema Defaults

Parameters with `default` values are optional. If not provided by the caller, the default is applied before resolution.

### 6.5. Sensitive Parameters

Parameters marked `sensitive: true` MUST be redacted in log output.

---

## 7. Safety Model

### 7.1. Policy

Execution runs under a named policy that controls:

| Field | Type | Meaning |
| --- | --- | --- |
| `name` | `str` | Policy name |
| `allowed_commands` | `set[str]` | Commands allowed by `system.run` |
| `blocked_patterns` | `tuple[str]` | Tokens blocked in system command arguments |
| `allow_network` | `bool` | Whether `net.request` is available |
| `allow_delete` | `bool` | Whether `file.delete` is available |
| `allowed_domains` | `set[str] \| None` | Allowed network hostnames (None = all) |
| `max_file_bytes` | `int` | Maximum file size for read/write |
| `allowed_capabilities` | `set[str] \| None` | Allowed capability tags (None = all) |

### 7.2. Built-in Policies

| Policy | Network | Delete | Capabilities | Commands |
| --- | --- | --- | --- | --- |
| `standard` | yes | yes | all | ls |
| `strict` | no | no | filesystem, memory, data, utility | ls |
| `dev` | yes | yes | all | python, node, ls |

`standard` is the safer default profile for AI-facing or deployed use.

`dev` is the relaxed local-development profile for trusted iteration.

### 7.3. Capability Enforcement

Before dispatching any action, the engine MUST check that all of the action's capabilities are in the policy's `allowed_capabilities` set (if the set is not null). If any capability is denied, the engine MUST raise `ACLSafetyError`.

### 7.4. File Safety

All file paths MUST be resolved to absolute paths and verified to be inside the workspace root directory. Paths that escape the workspace via `..` or symlinks MUST be rejected.

### 7.5. Command Safety

`system.run` MUST:

- verify the command is in `allowed_commands`
- check all tokens (command + args) against `blocked_patterns`
- verify `cwd` (if provided) is inside the workspace
- execute with `shell=False` (no shell injection)

When `command="python"` is allowed, an implementation MAY resolve it to the active interpreter path if no bare `python` executable is present on `PATH`.

### 7.6. Network Safety

`net.request` MUST:

- check `allow_network` policy flag
- verify URL hostname against `allowed_domains` (if set)
- apply the same host restriction after query-string expansion

### 7.7. Safety After Resolution

Safety checks MUST run on resolved parameter values, not raw values. This prevents bypass via variable indirection.

---

## 8. Error Taxonomy

All errors MUST carry a `.code` string for programmatic handling.

| Error Type | Code | Meaning |
| --- | --- | --- |
| `ACLParseError` | `parse_error` | Syntax error in ACL text |
| `ACLValidationError` | `validation_error` | Schema mismatch, unknown action, or constraint violation |
| `ACLVariableError` | `variable_error` | Undefined variable or failed selector resolution |
| `ACLSafetyError` | `safety_error` | Policy violation |
| `ACLExecutionError` | `execution_error` | Action implementation failure |
| `ACLTimeoutError` | `timeout_error` | Action exceeded time limit |

### 8.1. Error Attributes

- `ACLParseError`: `message`, `line_number`, `column`, `code`
- `ACLValidationError`: `message`, `action`, `line_number`, `code`
- `ACLVariableError`: `name`, `code`
- `ACLSafetyError`: `message`, `action`, `code`
- `ACLExecutionError`: `message`, `action`, `code`, `retryable`
- `ACLTimeoutError`: `message`, `action`, `code`, `retryable=True`

---

## 9. Output Contract

Every execution MUST return a JSON-serializable object with this exact structure:

```json
{
  "acl_version": "1.0",
  "output_version": "1.2",
  "status": "success" | "error",
  "trace_id": "<uuid-string>",
  "data": { "<assigned_variable>": <value>, ... },
  "logs": [ <log_entry>, ... ],
  "summary": { ... },
  "error": <error_object> | null
}
```

### 9.1. Status

- `"success"` — all commands completed without error
- `"error"` — at least one command failed

### 9.2. Data

Contains all variable assignments made during execution. Keys are variable names, values are action results.

### 9.3. Log Entries

Each executed command produces a log entry:

```json
{
  "step": 1,
  "line": 2,
  "action": "file.read",
  "raw_params": { "path": "main.py" },
  "resolved_params": { "path": "main.py" },
  "status": "success" | "error",
  "duration_ms": 5,
  "meta": { "reason": "debug" },
  "result": "file content...",
  "error": null
}
```

- `raw_params` — parameters after schema defaults, before variable resolution
- `resolved_params` — parameters after variable resolution
- Sensitive parameters MUST be replaced with `"<redacted>"` in both param fields

### 9.4. Summary

```json
{
  "started_at": "2024-01-01T00:00:00+00:00",
  "finished_at": "2024-01-01T00:00:01+00:00",
  "duration_ms": 1000,
  "steps_total": 3,
  "steps_completed": 2,
  "steps_failed": 1,
  "policy": "standard"
}
```

### 9.5. Error Object

When status is `"error"`:

```json
{
  "type": "ACLSafetyError",
  "message": "path '/etc/passwd' is outside the workspace",
  "code": "safety_error",
  "action": "file.read",
  "retryable": false
}
```

When status is `"success"`, the `error` field MUST be `null`.

### 9.6. Trace ID

Every execution MUST include a `trace_id`. If not provided by the caller, the engine generates a UUID v4. The trace ID appears in the top-level output and is available to actions via the runtime object.

---

## 10. Plugin Architecture

### 10.1. Plugin Module Contract

A plugin is a Python module (or equivalent in other languages) that exports a `register(registry)` function. This function receives the action registry and registers one or more actions.

```python
def register(registry):
    registry.register(
        "myplugin.action",
        my_action_func,
        {"param": {"type": str, "description": "..."}},
        capabilities={"utility"},
        description="My custom action",
    )
```

### 10.2. Loading

The engine provides `load_plugin_module(path)` which:

1. Loads the module from the file path
2. Calls `register(registry)` if it exists
3. Raises `ImportError` if no `register` function is found

### 10.3. Isolation

Plugin actions are subject to the same validation, safety, and capability rules as built-in actions. There is no privilege escalation path.

---

## 11. Implementation Requirements

### 11.1. MUST

- Execute commands sequentially in declaration order
- Validate before and after resolution (dual validation)
- Enforce safety on resolved values
- Produce the output contract defined in Section 9
- Support all error types in Section 8
- Enforce capability-based permissions

### 11.2. MUST NOT

- Reorder commands
- Allow silent variable resolution failures
- Execute shell commands with `shell=True` or equivalent
- Allow file access outside the workspace
- Add loops, conditionals, or nested execution to the language

### 11.3. SHOULD

- Cache parsed commands for repeated execution
- Pre-check variable references during validation
- Include timing information in logs and summary

### 11.4. MAY

- Support additional value constraints beyond those in Section 5.3
- Implement parallel action dispatch (only if declared safe by the action)
- Add transport-specific features (e.g., streaming output)

---

## 12. Versioning

### 12.1. ACL Version

The `acl_version` field identifies the language specification version. Changes to syntax or semantics require a new version.

### 12.2. Output Version

The `output_version` field identifies the output contract version. Changes to the JSON structure require a new output version. Consumers SHOULD check `output_version` before parsing.

### 12.3. Compatibility

Minor version changes (1.0 → 1.1) are additive. Major version changes (1.x → 2.0) may break compatibility.

---

## 13. Built-in Actions Reference

### File Actions (`filesystem` capability)

| Action | Parameters | Returns | Description |
| --- | --- | --- | --- |
| `file.read` | `path: str` | `str` | Read UTF-8 text file |
| `file.read_json` | `path: str` | `any` | Read and parse a JSON file |
| `file.write` | `path: str, content: any` | `{path, written}` | Write content to file |
| `file.write_json` | `path: str, value: any, indent: int? = 2, sort_keys: bool = false` | `{path, written}` | Serialize and write a JSON file |
| `file.append` | `path: str, content: any` | `{path, appended}` | Append content to file |
| `file.list` | `path: str = "."` | `list[str]` | List directory entries |
| `file.delete` | `path: str` | `{path, deleted}` | Delete a file |
| `file.exists` | `path: str` | `bool` | Check path existence |
| `file.stat` | `path: str` | `{path, exists, ...}` | Get path metadata |
| `file.mkdir` | `path: str` | `{path, created}` | Create directory |
| `file.copy` | `source: str, destination: str, overwrite: bool = false` | `{source, destination, copied}` | Copy a file |
| `file.move` | `source: str, destination: str, overwrite: bool = false` | `{source, destination, moved}` | Move a file |
| `file.glob` | `path: str = ".", pattern: str = "*"` | `list[str]` | Find matching workspace paths |

### System Actions (`process` capability)

| Action | Parameters | Returns | Description |
| --- | --- | --- | --- |
| `system.run` | `command: str, args: list = [], cwd: str? = null` | `{stdout, stderr, code}` | Run whitelisted command |

### Network Actions (`network` capability)

| Action | Parameters | Returns | Description |
| --- | --- | --- | --- |
| `net.request` | `method: str, url: str, headers: dict? = null, query: dict? = null, body: any? = null, json_body: any? = null` | `{status, ok, method, url, content_type, headers, body, json}` | HTTP request with automatic JSON parsing |

### Memory Actions (`memory` capability)

| Action | Parameters | Returns | Description |
| --- | --- | --- | --- |
| `memory.store` | `key: str, value: any` | `{key, stored}` | Store value |
| `memory.get` | `key: str` | `any` | Retrieve value |
| `memory.delete` | `key: str` | `{key, deleted}` | Delete value |
| `memory.list` | (none) | `list[str]` | List keys |
| `memory.clear` | (none) | `{cleared}` | Clear all |

### Data Actions (`data` capability)

| Action | Parameters | Returns | Description |
| --- | --- | --- | --- |
| `data.from_json` | `value: str` | `any` | Parse JSON text |
| `data.to_json` | `value: any, indent: int? = 2, sort_keys: bool = false` | `str` | Serialize a value as JSON |
| `data.merge` | `left: dict, right: dict, deep: bool = false` | `dict` | Merge two objects |
| `data.template` | `template: str, values: dict` | `str` | Render a string template |
| `data.select` | `value: any, path: str` | `any` | Select a nested value by path |
| `data.pick` | `value: dict, keys: list[str]` | `dict` | Keep only selected keys |
| `data.omit` | `value: dict, keys: list[str]` | `dict` | Remove selected keys |

### Utility Actions (`utility` capability)

| Action | Parameters | Returns | Description |
| --- | --- | --- | --- |
| `util.echo` | `value: any` | `any` | Return value unchanged |
| `util.sleep` | `seconds: number` | `{seconds, slept}` | Bounded sleep |
| `util.now` | (none) | `str` | UTC ISO timestamp |
| `util.uuid` | (none) | `str` | Random UUID v4 |
| `util.hash` | `value: any, algorithm: str = "sha256"` | `str` | Hash value |
| `util.base64_encode` | `value: any` | `str` | Base64 encode |
| `util.base64_decode` | `value: str` | `str` | Base64 decode |

---

## 14. Examples

### Basic

```acl
util.echo(value="Hello, World!") -> greeting
memory.store(key="msg", value=greeting)
```

### File Workflow

```acl
@reason="create and read back"
file.write(path="notes.txt", content="important data")
file.read(path="notes.txt") -> content
util.echo(value=content) -> output
```

### Variable Chaining with Selectors

```acl
system.run(command="ls", args=["."]) -> listing
util.echo(value=listing.stdout) -> files
```

### JSON Workflow Without Manual Serialization

```acl
util.echo(value="{\"user\": {\"id\": 7, \"name\": \"Ada\"}}") -> raw
data.from_json(value=raw) -> payload
data.select(value=payload, path="user.name") -> name
file.write_json(path="user.json", value={user=payload.user})
file.read_json(path="user.json") -> stored
```

### Multiline Parameters

```acl
file.write(
    path="config.json",
    content="{\"key\": \"value\"}"
) -> result
```

### Triple-Quoted Strings

```acl
file.write(path="poem.txt", content="""Roses are red
Violets are blue
ACL is simple
And so are you""")
```
