Built by Boundary as a reference for Alien; not an official Alien SDK.
One BAML project, four SDKs. The logic in baml/ is written once in
BAML; baml generate turns it into a TypeScript, Python, Go and Rust package.
Each package runs the same BAML code inside an embedded BAML runtime, so the
four SDKs behave identically by construction. A thin hand-written facade per
language adds client construction, idiomatic names and error conversion, and
nothing else.
This is a reference repository, not a release: nothing here is published, and
package names (@alien-baml/sdk, alien_sdk, example.com/alien-baml-sdk/go)
are placeholders.
It covers:
- Platform and Manager API operations imported from Alien's OpenAPI specs
(
specs/) bytools/openapi2baml: all 332: 279 Platform (every one Alien's TypeScript SDK would generate) and 53 Manager operations, in the shape docs/api-shape.md describes; - a setup-link helper that builds a customized AWS CloudFormation quick-create link from a server-provided template URL;
- the decode rules that the regression tests of Alien's Platform Rust client
check (unknown fields and enum values, optional success bodies,
storedSecretInputIdspresence, per-platform stacks, event states, roles), ported tobaml/baml_src/ns_compat/and run in all four languages; - a repro of two BAML-generated SDKs in one process (
examples/coexistence/).
Not covered:
- Alien's commands client (
@alienplatform/commands, withCommandsClientandinvoke, and the Rustalien-commands-client). The SDK has the operations it calls (commands.bootstrapon the Platform API,commands.createCommandandcommands.getCommandStatuson the Manager), not theinvokeflow built on them; - Alien's bindings (
alien-bindingsand its Node and Python adapters). docs/bindings/ compares the two adapters and the ways to produce them; - publishing and release: no package is published, and the package names are placeholders.
specs/ Alien's OpenAPI specs, vendored (see specs/README.md)
tools/openapi2baml/ the OpenAPI → BAML importer (scripts/import.sh)
baml/ the BAML project: baml.toml + baml_src/ns_*/ (ns_platform, ns_manager imported)
sdk/typescript/ generated/ (generated) src/ (facade) test/
sdk/python/ alien_sdk/ (facade; the generated SDK is alien_sdk/_generated/) tests/
sdk/go/ alienapi/ (generated) alien/ (facade + tests)
sdk/rust/ alien_api/ (generated) src/ (facade crate) tests/
conformance/ language-neutral scenarios, stub server, fixtures
docs/ API shape, errors, gaps, importer coverage, telemetry;
bindings/ compares Alien's Node and Python bindings adapters
bench/ benchmark programs (ours and Alien's current SDKs); bench/run.sh measures
metrics/ size, startup, per-call, large-page and Rust build measurements (written by bench/run.sh)
scripts/ import.sh, generate.sh, verify.sh
Each generated SDK is named by package_name in baml/baml.toml (BAML's
default is baml_sdk; these names are placeholders): the Python one lives
inside the facade's package, as alien_sdk._generated, so installing
alien-sdk writes no top-level package that another BAML-generated SDK would
overwrite (gaps B4). To rename one, change it there and in
the facade's imports of it (Python alien_sdk/_gen.py, the TypeScript and Go
imports, the Rust path dependency in sdk/rust/Cargo.toml and its uses);
each language's genops finds the generated package through the facade (so
the Go facade's hand-written files import exactly one package of the module,
the generated one).
The generated directories are not checked in, nor are the facades'
generated methods (sdk/python/alien_sdk/_operations.py,
sdk/go/alien/operations.go, sdk/rust/src/operations.rs). Never edit them, nor
the imported baml/baml_src/ns_platform/ and ns_manager/: change the
importer or its config and run scripts/import.sh, or change the hand-written
namespaces, then run scripts/generate.sh.
For coding agents, BAML's agent skill is checked in: .claude/skills/baml-core/
for Claude Code and .agents/skills/baml-core/ for Codex and OpenCode. It
teaches the BAML language and CLI of the pinned toolchain, and
baml agent install at the repository root writes it.
Tested on macOS arm64 with Node 24.22, pnpm 12.14, Python 3.14, uv 0.13, Go 1.27 and Rust 1.96.
bamlon PATH: the BAML wrapper (brew install boundaryml/baml/baml). The scripts install BAML 0.25.3-nightly.20261109.a through it- Node and pnpm
- Python 3.10+ and uv
- Go 1.24+ with cgo (
CGO_ENABLED=1, the default on macOS);baml generaterunsgofmt - Rust 1.91.1+
The scripts use BAML 0.25.3-nightly.20261109.a (BAML_PIN in scripts/lib.sh), which the
manifests pin exactly (npm @boundaryml/baml-bridge, PyPI baml-bridge, Go
github.com/boundaryml/baml-go, crates.io baml_bridge). It is a nightly
build of BAML, not a numbered release: the imported types use @json
attributes (BAML #5563), which no numbered release contains. PyPI spells the
version 0.25.3.dev2026110900. The scripts keep
all BAML state in the repository (.baml_home/, .cache/), never in
~/.baml.
git clone <this repository> alien-baml-sdk && cd alien-baml-sdk
scripts/generate.shgenerate.sh regenerates the four SDKs and builds the facades. Each example
below builds a CloudFormation quick-create link, calls no API and prints:
https://us-east-1.console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/create/review?templateURL=https%3A%2F%2Fexample.com%2Fsetup.yaml&stackName=alien-setup¶m_ExternalId=customer-0001
TypeScript. Save as sdk/typescript/quickstart.mjs, then run cd sdk/typescript && node quickstart.mjs.
import { cloudFormationLaunchUrl } from "@alien-baml/sdk"
console.log(cloudFormationLaunchUrl({
templateUrl: "https://example.com/setup.yaml", region: "us-east-1",
stackName: "alien-setup", parameters: { ExternalId: "customer-0001" },
}))Python. Save as sdk/python/quickstart.py, then run cd sdk/python && uv run python quickstart.py.
import alien_sdk
print(alien_sdk.cloudformation_launch_url(alien_sdk.CloudFormationLaunch(
template_url="https://example.com/setup.yaml", region="us-east-1",
stack_name="alien-setup", parameters={"ExternalId": "customer-0001"})))Go. Save as sdk/go/cmd/quickstart/main.go, then run
cd sdk/go && go run ./cmd/quickstart.
Go and Rust load BAML's runtime library at run time: the first program on a
machine downloads it into ~/.baml/bridges/0.25.3-nightly.20261109.a/, and later ones load that
copy. docs/runtime.md has how to provide it ahead of time.
package main
import (
"context"
"fmt"
"log"
"example.com/alien-baml-sdk/go/alien"
)
func main() {
defer func() { // seals the telemetry recording
if err := alien.Shutdown(); err != nil {
log.Print(err)
}
}()
url, err := alien.CloudFormationLaunchURL(context.Background(), alien.CloudFormationLaunch{
TemplateUrl: "https://example.com/setup.yaml", Region: "us-east-1",
StackName: "alien-setup", Parameters: map[string]string{"ExternalId": "customer-0001"},
})
if err != nil {
panic(err)
}
fmt.Println(url)
}Rust. Save as sdk/rust/examples/quickstart.rs, then run
cd sdk/rust && cargo run --quiet --example quickstart.
It prints nothing else: BAML's Rust bridge logs through the log crate
(target baml_bridge) to whatever logger the program installs, such as
env_logger (RUST_LOG=baml_bridge=debug shows how it finds and loads the
runtime library) or a tracing subscriber with tracing-log, which
tracing-subscriber enables by default. BAML_LOG does not change it.
#[tokio::main]
async fn main() -> Result<(), alien_sdk::Error> {
let url = alien_sdk::cloudformation_launch_url(alien_sdk::CloudFormationLaunch::new(
"https://example.com/setup.yaml".into(), "us-east-1".into(), "alien-setup".into(),
[("ExternalId".to_string(), "customer-0001".to_string())].into_iter().collect(),
)).await?;
println!("{url}");
Ok(())
}Each SDK runs an embedded runtime, which seals its telemetry recording when it
shuts down (Telemetry; with ALIEN_TELEMETRY=off none of this
matters). Python and Rust shut the runtime down when the process exits
normally, TypeScript when Node's event loop runs empty; Go never does it by
itself. Call the SDK's shutdown last wherever that does not happen:
process.exit(), a signal handler, an abort, a serverless platform ending the
process, and every Go main (as above).
| Language | Call | Automatic at |
|---|---|---|
| TypeScript | await shutdown() |
Node's beforeExit; not on process.exit() or signals |
| Python | alien_sdk.shutdown() |
interpreter exit (atexit); not on os._exit() or SIGTERM |
| Go | defer alien.Shutdown() |
never |
| Rust | alien_sdk::shutdown()? |
normal exit; not on signals or abort |
SDK calls made after it fail. Details: docs/runtime.md.
The client defaults to https://api.alien.dev. The examples above do not call
it; these sketches show the shape of the API (the conformance suites run the
same calls against a local stub server).
Every operation is <group>.<method> as in Alien's TypeScript SDK
(docs/api-shape.md):
const alien = new Alien({ workspace: "my-workspace" }) // apiKey defaults to ALIEN_API_KEY, as in Alien's SDK
const manager = await alien.managers.get("mgr_...")
const page = await alien.deployments.list({ project: "my-project", limit: 10 })
const url = await alien.setup.launchUrlForProject("my-project", "us-east-1", "alien-setup", { ExternalId: "customer-0001" })alien = alien_sdk.Alien(workspace="my-workspace") # api_key defaults to ALIEN_API_KEY
manager = alien.managers.get("mgr_...")
page = alien.deployments.list(project="my-project", deployment_group="prod", limit=10)
page.next_cursor # groups, methods, keywords and fields are snake_case
# Methods are typed (mypy and editors see each signature); a wrong keyword raises TypeError.
from alien_sdk.platform import CreateSetupLinkRequest # request and response types, per API
request = CreateSetupLinkRequest.model_validate(form) # camelCase keys (the API's, with the exceptions in docs/gaps.md#b27) or snake_caseclient, err := alien.New(alien.Options{Workspace: "my-workspace"}) // API key from ALIEN_API_KEY
manager, err := client.Managers.Get(ctx, "mgr_...")
page, err := client.Deployments.List(ctx, &alien.ListDeploymentsParams{Project: alien.Ptr("my-project"), Limit: alien.Ptr[int64](10)})use alien_sdk::{Alien, Options};
// The API key comes from ALIEN_API_KEY unless `api_key: Some(..)` is set;
// with neither, `new` fails with INVALID_REQUEST.
let alien = Alien::new(Options { workspace: Some("my-workspace".into()), ..Default::default() })?;
let manager = alien.managers().get("mgr_...").send().await?;
// Required arguments go to the method, optional ones to the builder's setters.
let page = alien.deployments().list().project("my-project").limit(10).send().await?;Each client also takes a limit for each HTTP request, BAML's 5 minutes when
unset: TypeScript timeoutMs, Python timeout= (seconds; None for no
limit), Go Options.Timeout (a time.Duration), Rust Options::timeout (a
Duration); a request that runs out fails with COMMUNICATION_ERROR
(decision 9 in docs/api-shape.md).
Every call fails with one error type, AlienError (code, message,
retryable, HTTP status, context). See docs/errors.md.
Keep the generated clients out of logs. The clients you create (Alien
and AlienManager; in Go *alien.Client and *alien.ManagerClient) do not
print the API key or the token. The generated clients they hold do:
- Rust: none is reachable.
Alien,AlienManagerand every group and builder print without the key or token, so they are safe indbg!, logs and#[tracing::instrument]arguments. - Go:
%v,%+vand%#vofclient.Platformshow the API key, solog.Printf("%+v", client.Platform)logs it.json.Marshalof a client or of its generated client writes the key or token. - Python: a traceback that records local variables, as Sentry does by default, holds the API key (docs/errors.md).
Each recipe uses the generated code, so run scripts/generate.sh first. The
TypeScript and Python packages are copies: pack or install them again after
regenerating. In your own project (/path/to/alien-baml-sdk is this
repository):
- TypeScript. Pack the package and install the tarball, as
examples/coexistence/run.shdoes:(cd /path/to/alien-baml-sdk/sdk/typescript && pnpm pack --pack-destination /path/to/packs), thenpnpm add /path/to/packs/alien-baml-sdk-0.0.0.tgz(ornpm installthe same file). pnpm 12 fails the install over the install script ofprotobufjs, a dependency of the bridge that only prints a notice, unless yourpnpm-workspace.yamlhasallowBuilds: { protobufjs: false }. - Python.
uv pip install /path/to/alien-baml-sdk/sdk/python(orpip install …) into your environment, thenimport alien_sdk. - Go.
go mod edit -replace example.com/alien-baml-sdk/go=/path/to/alien-baml-sdk/sdk/go, thengo get example.com/alien-baml-sdk/go/alien. - Rust. A path dependency:
alien_sdk = { path = "/path/to/alien-baml-sdk/sdk/rust" }.
Each recipe brings the published BAML 0.25.3-nightly.20261109.a runtime package.
The BAML runtime records spans, timings and HTTP calls for every SDK function
without instrumentation code. Telemetry is on by default (medium);
ALIEN_TELEMETRY=off turns it off (the other levels are low, medium
and high). Without a Boundary API key it writes local files under
$BAML_HOME/btel/ (~/.baml by default), which nothing deletes, and makes no
connection, unless the machine has a saved Boundary login (baml auth login):
then it uploads with that login (gaps B3). It never records
HTTP request or response bodies, which carry tokens (http_bodies = false in
baml/baml.toml). A process that ends without shutting the runtime down
leaves its recording unsealed: see Shutting down. The SDK
never prints telemetry warnings: if the endpoint cannot be reached or rejects
the key, telemetry is dropped silently. Details and the checks behind these
statements: docs/telemetry.md.
scripts/verify.sh --phase 2Runs every check: tests the importer and checks that the imported BAML and
the generated operation scenarios are current, runs baml check and
baml test, regenerates and builds the four SDKs, runs all 883 conformance
scenarios in all four languages, checks the shared setup fixture, runs the
README quickstarts, the telemetry checks, the B4
coexistence repro and a secret scan, and prints a summary table. It exits
non-zero on the first failure. --phase 1 leaves out the importer checks and
runs only the 49 hand-written scenarios, not the 834 generated ones that call
each operation (conformance/README.md).
docs/gaps.md lists what BAML could not do for this SDK, with a minimal repro and the workaround used here or the BAML pull request that fixes it, plus open questions for Alien.
specs/ is copied from alienplatform/alien at c5c3a30
(v3.3.43). baml/baml_src/ns_platform/, baml/baml_src/ns_manager/,
conformance/scenarios/ops.* and docs/importer-coverage.md are generated
from those specs, tools/openapi2baml/tests/fixtures/naming-oracle.json from
Alien's TypeScript SDK, and baml/baml_src/ns_compat/ ports tests of Alien's
Rust client. That material is Copyright 2026 Alien Software, Inc., under the
FSL-1.1-Apache-2.0.