Using packages
A project in AEL, the Agent Engineering Language, declares the packages it uses in pack.ael, and AEL records exactly what it resolved in pack.lock. Your source names the parts of a package it uses, and only those parts reach your program.
The manifest
Every project has a pack.ael at its root, even when it uses no packages. This is a complete manifest:
pack { schema = 1; dependencies = []; }
A project that serves HTTP adds the official server/http package:
pack {
schema = 1;
dependencies = [
{ name = "server/http"; version = "^1.2.0"; kind = "runtime";
source = { kind = "registry"; };
features = []; targets = []; capabilities = ["net.listen"];
publisher = "openeng"; }
];
}
Each dependency names every field explicitly, so a manifest reads the same to everyone. A manifest is a declaration, never code: nothing in it runs. The Manifest reference describes every field.
Folder scopes
The root pack.ael covers the whole project. Any folder can have its own pack.ael, which covers that folder and the folders below it:
support/
main.ael
pack.ael # server/http for the whole project
pack.lock
metadata.ael
.package/ # generated: the root scope's installed packages
api/
pack.ael # its own version of server/http, for api/ and below
pack.lock
.package/
jobs/ # no manifest: uses the root's server/http
- The nearest manifest that declares a package wins. A file in
api/uses the version inapi/pack.ael; a file injobs/uses the root's. - A folder's manifest adds; it never hides. Packages declared further up stay available unless the folder declares the same package itself.
- Siblings and sub-folders never supply packages. A file in the root cannot use a package declared only in
api/pack.ael, andjobs/never seesapi/'s packages. - No silent fallback. If the nearest declaration does not fit, the check fails; AEL never falls back to a version further up that happens to fit.
- Each manifest has its own lock file and installed packages, beside it:
pack.lockand.package/. A scope with no packages needs no.package/.
Two folders can use different versions of one package only when those versions can work side by side in one program; when they cannot, AEL reports the conflict instead of picking one.
Managing packages
ael pack manages a project's packages:
| Command | What it does |
|---|---|
ael pack add <name>[@<version>] | Adds a new package to a manifest, resolves it, installs it and writes the lock file, all at once. |
ael pack install | Installs exactly what pack.ael and pack.lock already say, changing no version. --locked refuses to change the lock file; --offline uses verified local copies only. |
ael pack update <name> | Moves a package to another version its requirement allows, and shows what else changes. |
ael pack remove <name> | Removes a package, keeping the packages that others still need. |
ael pack lock | Resolves the manifest and writes the lock file, without installing anything. |
ael pack list | Lists the packages that are declared, installed and actually used. |
ael pack why <name> | Explains why a package is in the project, and through which path. |
ael pack verify | Checks the installed packages against the lock file. |
add is for a package new to the project; install sets up what is already declared and locked, such as on a new machine. add, install, update, remove, lock and list take --scope <folder> to choose which folder's manifest they act on; without it, they use the manifest that covers the folder you are in. Adding with --scope to a folder without a pack.ael can create one there. Every change reports the scope it acted on, and every command takes --json for machine-readable output.
ael pack add server/http@1.2.3
ael pack add math/linalg --scope src/analysis
ael pack install --locked
ael pack update server/http
ael pack remove server/http
Every change is atomic. The manifest, the lock file and the installed packages change together, and an interrupted change leaves the complete old state or the complete new one, never a mix. Adding or updating a package shows the dependencies and capabilities it brings before anything changes.
Removing a package never edits your source. If a file still refers to the package, the next check fails and names the reference, so you can remove it deliberately. If you remove a package from a folder's own manifest and a manifest further up still declares it, the files of that folder then use that one, and the command tells you so.
Using a package in your source
A file names the package exports it uses in its header, before its first declaration:
# src/api/billing.agent.ael
refer server/http select get;
@get
agent billing(input: Request) -> Decision {
# The selected hook export is attached to this agent.
}
This example shows only how a file selects an export and attaches it, here the hook get. It is not how you serve an HTTP route: routes live in a .routes.ael file that main mounts, as Services and APIs shows.
refernames the package;selectnames the exports this file uses, separated by commas.- Every selected export has to be used. A call, a value reference or a hook attachment in code that can run counts. A comment, a string, a helper nobody calls or code after a
returndoes not. Selectingget, putand using onlygetfails the check. - A package never adds names to your folders. Its helpers are never visible, and its exported functions only where you select them by name.
- A package that is installed but never selected is allowed; the reference map reports it as unused.
Only what you use ships
A build keeps only what your program can reach from main. A console agent needs no server: add server/http and declare routes to make it a REST API, or remove them to go back to console-only; Services and APIs shows both. An HTTP-only app carries no WebSocket code, and no gRPC code unless an edge is placed on the network, and a GET-only API no POST handlers.
ael refmap shows what references what, for the whole project, one folder or one file, and lists unused code and packages.
The lock file and offline builds
pack.lockrecords the exact version, origin and fingerprint of every package a scope uses, its dependencies, and the features and targets chosen. It is generated; never edit it by hand.- Keep
pack.lockwith your source files, and leave.package/out:ael pack install --lockedrecreates it on any machine from the lock file. ael pack install --locked --offlinedoes not use the network: it installs from verified local copies and names any package file it is missing.- Exporting for offline builds covers machines without a network connection, and Pruning the package cache clearing cached packages that no project uses.
- Checking, compiling and building use only the locked, installed packages: they never contact the registry, change a version or install anything. Nothing downloads when an app starts. A missing package is an error that tells you to run
ael pack install.