Browse the documentation

AEL 1.0.0

Functions in other languages

In AEL, the Agent Engineering Language, your application can hold private functions written in other languages, with typed AEL signatures, built and run with your own toolchains, pinned by version. You write the body in its own language and the signature in AEL, so the rest of your program calls the function like any other, and the check holds every call to its types. This page describes functions in other languages in AEL 1.0.0.

Two ways to write one

FormWhere it is writtenWhat it declares
A language file<name>.<lang>.ael, such as profile.python.aelOne function in that language, <lang> <name>(…), as the file's primary.
An inline functionmain.ael, the file of any component except a hook, a configuration or a skill, and their extension filesfn <lang> <name>(…), beside your AEL functions.

<lang> is the tag of a language from the list of languages, in lower case, such as python, bash or golang.

A language file

# src/profile.python.ael
python profile(x: i64) -> i64 [version = "3.12", mode = machine] {
    return x * 2
}
  • The primary starts with the language's tag, then the file's name: profile.python.ael declares python profile(…), and the check refuses another language or another name.
  • The signature, (x: i64) -> i64, is AEL. The body, between the braces, is Python.
  • A language file can also hold private helper functions, in AEL or in its own language.

Its extension files hold more functions of the same language, in the same folder:

src/
  profile.python.ael
  profile.python.extended.ael
  profile.python.extended.2.ael
# src/profile.python.extended.ael
fn python clamp(x: i64, limit: i64) -> i64 {
    return min(x, limit)
}
  • An extension file of a language file holds only fn <lang> functions of that file's language: no AEL function, no other language and no header.
  • Its functions belong to the language file, as the helpers of any extension file belong to its component; see Extension files.

An inline function

# src/nodes/score.node.ael
node score(points: i64) -> i64 {
    return scale(points, 3);
}

fn python scale(p: i64, k: i64) -> i64 {
    return p * k
}
  • fn comes first, then the language's tag, the name and the AEL signature.
  • An inline function sits at the top level of its file, beside your AEL functions, never inside another function.
  • A component's extension files can hold inline functions in any language of the list, beside its AEL helpers. The extension files keep their names: score.node.extended.ael holds fn python functions without a language in its name.

Names that are refused

  • A language and a role in one name, in either order: profile.python.reuse.ael and profile.reuse.python.extended.ael are refused. A component's file and its extension files never carry a language; they hold fn <lang> functions instead.
  • A language that is not in the list, or a tag in another letter case, such as profile.Python.ael.
  • A role that collides with a language. No language takes the name of a role, such as routes, and a role that a package adds can never take the name of a language, so the check never has to guess which one a file means.
  • An extension numbered 1 or with a leading zero, such as profile.python.extended.1.ael, as for every extension file.

Private, always

  • Every function in another language is private to the file that declares it and that file's extension files.
  • pub fn python is refused, like every pub fn. No import, reference or function value makes such a function public.
  • The primary of a language file is private to that file and its extension files too.

Other files reach your code in another language through a function written in AEL: a reusable or global function that wraps a private helper in that language.

# src/scoring.reuse.ael
reuse scoring(points: i64) -> i64 {
    return weigh(points);
}

fn python weigh(points: i64) -> i64 {
    return points * 3 + 1
}
# src/nodes/rank.node.ael
import "../scoring.reuse.ael";

node rank(points: i64) -> i64 {
    return scoring(points);
}

rank calls scoring through its import and never sees weigh. Functions and visibility gives the sharing rules that every function follows.

Options

One optional record in square brackets follows the result type, before the {, as in [version = "3.12", mode = machine]. It holds at most these three fields, each one once, separated by commas:

OptionValueWhat it does
versionAn exact version in quotes: one to four numbers separated by dots, such as "3.12" or "21"The toolchain must report this version, compared at the precision you write: "3.12" takes a 3.12 release and never 3.13. Another version is refused, never substituted, and AEL never downloads a runtime. Without version, the version that is installed is used, and its exact identity is recorded.
modemachine, the default, or sandboxWhere the function runs: on the same machine as your program, with the toolchain you installed, or in a sandbox.
shellThe file name of a shell program, in quotesFor bash and shell functions only: the shell that runs the body. A name only, with no path, arguments or flags.
  • Ranges and wildcards, such as "3.x" or ">=3", are refused, and so are an unknown field, a field written twice and a trailing comma.
  • A function in another language runs on the same machine by default or in a sandbox, which needs the isolation facilities of Linux and runs only on the Linux target the documentation names for it, and a missing toolchain, a wrong version or a missing sandbox is refused before anything runs.
  • A function in sandbox mode never runs on the same machine instead. Supported targets names the target where the sandbox runs.
  • Machine mode does not mean unlimited permissions: what a function may use comes from your deployment, as Running describes.

Sandbox mode and isolated components

Isolated components run in a sandbox too. You mark an agent, node or subagent as isolated: it always runs in a sandbox in its own process, outside the shared communication layer, reachable only through the edges you declare and holding only the resources it declares itself. Isolation needs the isolation facilities of Linux and runs only on the Linux target the documentation names for it: on macOS, on Windows, on microcontroller boards, on other Linux targets and on any host without those facilities, an isolated component is refused, never run without isolation.

Inside an isolated component, a function in another language runs only in sandbox mode: one in machine mode, the default, is refused there.

The body

  • The line of the declaration ends with {, and the body starts on the next line.
  • The body ends at the first line that starts with } in its first column, with nothing after it but spaces or tabs.
  • Everything in between belongs to the other language, byte for byte: AEL reads no comment, string or escape inside it, and changes no indentation. An indented } is part of the body.
  • A body cannot hold a line that is only a } in its first column: indent that line, or write the code another way.
  • A body on the declaration's own line, and a file that ends before the closing line, are refused.

Every source file stays small and reviewable: a fixed size limit per file, with private helpers split into extension files. Bodies count toward the limits of their file, at most 5,000 bytes and at most 5,000 lines, with every byte of every body included. A body fits in one file and never continues in another, so when code grows, you split it into several functions in the file's extension files. Limits gives the exact rules.

Types a signature can use

The parameters and the result of a function in another language are typed in AEL, and every value crosses by value, in both directions:

AEL typeHow it crosses
()As a parameter or the result.
boolAs a parameter or the result.
i8, i16, i32, i64, u8, u16, u32, u64At its exact width and sign; every value keeps its precision, the largest and the smallest included.
Str<N>As valid UTF-8 text of at most N bytes.
Vec<T, N>As a list of at most N elements, whose T is one of the types above.
Result<T, ForeignError>As the result only, with T one of the types above.
  • Nothing else crosses: no reference, struct, enum, Option, nested Result, list of lists or function. The check refuses such a signature at its declaration, before any toolchain starts.
  • A function has at most 128 parameters, and a capacity N is at most 16,777,216.
  • A value that does not fit its type, such as text longer than N bytes, is a failure; it is never cut short.
  • A binary or assembly function uses only (), bool and the fixed-width integers.

ForeignError is AEL's own error type for these calls, and the only error type such a signature takes. It holds the status, up to 4,096 bytes of the function's standard error, and its kind:

KindWhat happened
LaunchThe function could not start.
ExitIt ended with a failure status.
SignalThe operating system stopped it.
TimeoutIt passed its deadline.
CancelledIt was cancelled.
DecodeIts result did not decode into the declared type.
BudgetIt reached a limit of its budget.
UnavailableIts toolchain or runtime was not available.

With a plain result type, a failure is never turned into a value of that type. Declare Result<T, ForeignError> to handle failures in your code.

Building

When you build, AEL checks every function in another language first, then compiles each one with its own toolchain:

  • Checked before any toolchain starts. File names, privacy, options, signatures, the limits of the bodies and what the target can offer are checked first.
  • Your toolchain, at its pinned version. Each function is compiled by the toolchain you installed for its language, at the version it pins. A missing toolchain or another version refuses the build, and nothing is written.
  • Compiled, never run. Building checks and compiles your code in the other language; it never runs your function. A Python function's source is checked and compiled to bytecode, a bash or shell function is checked for its syntax, and the other languages are checked or compiled by their own toolchains.
  • Every function. Every function you declare is compiled, including one that nothing calls; only what your program reaches goes into it.
  • Beside your program. The compiled code of your functions is written beside the program, and a failed build leaves no part of it behind.
  • Nothing downloaded. The build never downloads or installs a toolchain or a runtime.
  • Nothing without a declaration. A project that declares no function in another language starts no toolchain at all.

Building a program describes the build.

Running

  • The runtime you installed. Where your program runs, each function runs with the runtime its language needs, at the version it pins, in the mode it declares. A missing runtime, another version or a missing sandbox fails the call before the function starts.
  • Arguments stay data. Arguments are passed as typed values, never spliced into a command line or into the text of a script.
  • Results are checked. The result is decoded into its AEL type, and a result that does not decode is a Decode failure, never a value. A function's exit status is never read as its result.
  • Limits, deadlines and cancellation. The working directory, the environment values, the files, network and processes a function may use, and its time, memory and output limits come from your deployment, through the call that runs it, and the function draws on the budget of the run that calls it. A cancellation or a passed deadline stops the function and every process it started.
  • Unknown stays unknown. After a cancellation, an effect outside your program whose outcome is not known is reported as unknown, never erased.
  • No secrets in source. Credentials never appear in your source or in compiled files.
  • Only when you reach one. A runtime enters your program only when the program declares a function in another language that it reaches. A program without one carries nothing for them and needs no runtime.

Machine code: binary and assembly

binary and assembly need no toolchain of yours: AEL itself checks them, for the one target the function is written for.

  • A binary function's body is machine code, and AEL decodes and verifies every byte of it.
  • An assembly function's body is instructions, and AEL assembles and verifies each one.
  • AEL refuses code that it cannot verify, and code for another target.
  • These functions take no version.

What stays in AEL

  • main. main.ael and its extension files can hold fn <lang> functions that main calls, but main itself is written in AEL.
  • Hooks. A hook file and its extension files hold no function in another language; see Hooks.
  • Configuration and the control files. Configuration files, pack.ael and metadata.ael are AEL.
  • Skills. A skill holds AEL only; see Skills.
  • Packages. A package project that holds a language file or a function in another language is refused: functions in other languages belong to applications. See Packages.

A function, or a program you already have

A function in another language is code you write in your application, compiled with it when you build. To call a program that you already have, without changing it, your agent uses the execution packages instead. Calling your existing programs compares the two.

In AEL Cloud

In AEL Cloud, functions in other languages may be written in bash, shell or Python and always run in a sandbox, and a program that declares any other language is refused when you submit it. AEL Cloud describes the service.

Distributions

AEL 1.0.0 is the full distribution; smaller distributions of the same version leave out components, such as support for other languages or for agents, and refuse a feature they leave out by naming it. A distribution without support for other languages refuses every function in another language; Distributions describes them.

The languages

These are the languages you can write a function in, with the tag that names each one in a file name and after fn:

LanguageTagKind
AdaadaCompiled to native code
AssemblyassemblyVerified by AEL
BASICbasicCompiled to native code
BashbashInterpreted
BinarybinaryVerified by AEL
BunbunInterpreted
CcCompiled to native code
C#csharpCompiled for a managed runtime
C++cppCompiled to native code
ClojureclojureCompiled for a managed runtime
COBOLcobolCompiled to native code
CSScssRecognized, not callable
DartdartCompiled for a managed runtime
DelphidelphiCompiled to native code
ElixirelixirCompiled for a managed runtime
ErlangerlangCompiled for a managed runtime
F#fsharpCompiled for a managed runtime
FortranfortranCompiled to native code
Go / GoLanggolangCompiled to native code
GroovygroovyCompiled for a managed runtime
HaskellhaskellCompiled to native code
HTMLhtmlRecognized, not callable
JavajavaCompiled for a managed runtime
JavaScriptjavascriptInterpreted
JuliajuliaInterpreted
KotlinkotlinCompiled for a managed runtime
LisplispInterpreted
LualuaInterpreted
MATLABmatlabInterpreted
.NETdotnetCompiled for a managed runtime
Objective-CobjectivecCompiled to native code
PascalpascalCompiled to native code
PerlperlInterpreted
PHPphpInterpreted
PrologprologInterpreted
PythonpythonInterpreted
RrInterpreted
RubyrubyInterpreted
RustrustCompiled to native code
ScalascalaCompiled for a managed runtime
SchemeschemeInterpreted
ScratchscratchRecognized, not callable
ShellshellInterpreted
SQLsqlRecognized, not callable
SwiftswiftCompiled to native code
TypeScripttypescriptCompiled for a managed runtime
VerilogverilogRecognized, not callable
VHDLvhdlRecognized, not callable
  • HTML, CSS, SQL, VHDL, Verilog and Scratch are recognized but not callable: the check keeps their bodies unchanged, and refuses a call to one of them and a build that would run one.
  • AEL knows exactly these tags. It never guesses a language from a body, and never downloads a tool to add one.

Honest limits

  • AEL checks the signature, the options and the limits of a function in another language, not the code inside its body: what that code does is up to its own language and toolchain.
  • Sandbox mode needs the isolation facilities of Linux and runs only on the Linux target the documentation names for it. Wherever it does not run, a function that declares it is refused, never run on the same machine instead.
  • Each toolchain and runtime is yours to install, at the version your code pins, on the machine that builds the program and on the machine that runs it.