Introduction
aiSlang is a declarative language (.ais) plus a single CLI (aislang) for building multi-agent AI systems. One file describes the whole system, and one CLI validates it, compiles it, and runs it — locally on your own machine or as a stack you deploy to your own cluster. The full .ais grammar is defined in the language reference (LANGUAGE-SPEC-v0.md).
Agent frameworks make it easy to demo one agent. Real systems compose many — a router picks a specialist, an orchestrator delegates, a reflector critiques and retries — and need model fallback, spend caps, tracing, secret scoping, and a real way to serve them. aiSlang makes the system the unit: you declare the agents and how they combine, then aislang apply ships it.
One file describes the whole system
A .ais project captures everything about a multi-agent system in one place:
- Agents and how they compose — routing, orchestration, reflection, and other patterns.
- Models by capability, not vendor lock-in: declare what a model must do (
requires = [chat, tool_use],context_min,cost_max, latency, residency) and let the resolver pick a concrete model from a bundled catalog, withfallbackchains and per-run / per-day budgets. - Prompts, versioned and hashed, with template variables.
- MCP tools the agents can call.
- RAG knowledge — vector stores and embeddings attached to an agent.
- Evals — datasets, metrics, and pass thresholds.
- Deploy targets — Docker Compose or Kubernetes.
What the CLI does
The aislang CLI is a compile-then-deploy tool, not an interpreter:
- Validates and type-checks the
.aissource, with source-aware diagnostics. - Resolves models against a bundled catalog and pins the choices in a lockfile for reproducibility.
- Emits a stack — a Docker Compose or Kubernetes deployment made of a LiteLLM router, Jaeger tracing, budget enforcement, and your agents.
- Runs chat and evals locally against the deployed stack.
- Exposes its whole surface as an MCP server, so MCP-aware hosts like Claude Desktop or Cursor can author, deploy, and chat with your agents.
aislang remote --target can also talk to aiSlang Cloud, a managed control plane; these docs cover the local, self-hosted CLI only.
What these docs cover
- Why aiSlang — the problem it solves and where it sits relative to existing tools.
- Install — get the
aislangbinary and its prerequisites. - Quickstart — a running multi-agent stack in five minutes.
- Language — the
.aislanguage reference. - CLI reference — every subcommand and flag.
- Deploy — Docker Compose and Kubernetes targets.
- MCP — drive aiSlang from Claude Desktop, Cursor, and other MCP hosts.
- Observability — OpenTelemetry traces, Jaeger, and external collectors.
Why aiSlang
Agent frameworks make it easy to demo one agent. Real systems compose many — a router picks a specialist, an orchestrator delegates, a reflector critiques and retries — and those systems need model fallback, spend caps, distributed tracing, scoped secrets, and a real way to serve the agents to users. The gap between a working notebook and a deployed, observable, budget-enforced multi-agent service is where most of the work actually lives, and today there are only two ways to close it.
The first is imperative Python glue — LangChain, LlamaIndex, CrewAI, AutoGen. These are good libraries for expressing agent behavior, but you hand-write and maintain the orchestration code yourself, there's no declarative description of the infrastructure, and turning a notebook into a deployed service is entirely do-it-yourself: you still have to wire up a router, budgets, tracing, and serving by hand. The second is hand-rolled infrastructure — Kubernetes or Compose manifests plus a LiteLLM router plus OpenTelemetry plumbing plus budget enforcement, assembled from scratch. That works, but it's weeks of YAML and boilerplate per project, and it drifts the moment your agents change.
aiSlang is not another agent framework, and it doesn't compete with those tools — it's the layer beneath them. It's the declarative infrastructure layer that turns a described multi-agent system into a running, observable, budget-enforced stack on your own machine or cluster. You declare the agents, models, tools, knowledge, evals, and deploy target in one .ais file; the CLI type-checks it, resolves models against a catalog, and emits a real stack — a LiteLLM router, Jaeger tracing, fail-closed budget enforcement, and your agents — that you apply, chat with, and eval locally or ship to your own Kubernetes cluster. The system is the unit, and the file is the source of truth.
Install
One-liner
The installer detects your OS and architecture, fetches the matching tarball from GitHub Releases, verifies its sha256 checksum, and installs the aislang binary into $HOME/.local/bin:
curl -sSfL https://github.com/aiSlang/cli/releases/latest/download/install.sh | sh
Private repo / fork
If you're installing from a private repository or fork, create a GitHub PAT — a Classic PAT with repo scope, or a fine-grained PAT with read access to the single repo — at https://github.com/settings/tokens, then:
GITHUB_TOKEN=ghp_XXXX REPO=aiSlang/cli \
curl -sSfL -H "Authorization: Bearer $GITHUB_TOKEN" \
"https://raw.githubusercontent.com/${REPO}/main/install.sh" \
| GITHUB_TOKEN=$GITHUB_TOKEN REPO=$REPO sh
GITHUB_TOKEN (or GH_TOKEN) is honored for all release-asset downloads.
Where it installs
The binary lands in $HOME/.local/bin. Make sure that directory is on your PATH. Note that GUI tools (like Claude Desktop) don't inherit your shell PATH, so when you point one of them at aislang you must use an absolute path — find it with command -v aislang.
Check prerequisites
Probe for runtime prerequisites without installing anything. This reports on docker, docker compose, helm, kubectl, and kind, and prints per-distro install hints:
sh install.sh --check-deps
Prerequisites:
- Docker is required — the Compose deploy target and local chat both use it.
- helm, kubectl, and kind are only needed for the Kubernetes deploy target.
Build from source
cargo build --release
./target/release/aislang --help
Quickstart
This is the five-minute path from a .ais file to a running, chattable agent stack.
1. Write a .ais file
Save this as support-agent.ais. It declares one model (selected by capability, with a local Ollama fallback), a versioned prompt, an MCP filesystem tool over a knowledge directory, one agent, an eval, and a local Compose deploy target. (Every construct here is covered in The aiSlang language, and the full grammar is in the language reference (LANGUAGE-SPEC-v0.md).)
project "support-agent" {
budget {
hard_cap_per_day = $50
hard_cap_per_run = $0.50
optimize_for = cost
}
model "fast" {
requires = [chat, tool_use]
context_min = 32k
cost_max = $0.001 per 1k_tokens
latency_p95 = 2s
residency = [eu, us]
prefer = [anthropic.claude_haiku_4_5, openai.gpt_4o_mini]
fallback = [ollama.llama_3_1_8b]
}
prompt "system" {
file = "./prompts/system.md"
version = "1.0.0"
}
mcp_server "docs" {
image = "docker.io/mcp/filesystem:1.0.2"
mount = "./knowledge/"
}
agent "support" {
model = model.fast
system_prompt = prompt.system
tools = [mcp_server.docs]
}
eval "factual_accuracy" {
dataset = "./evals/qa.jsonl"
metric = f1
threshold = 50%
}
deploy "local" {
target = compose
expose {
port = 8080
web_ui = true
}
}
}
The fast model resolves to anthropic.claude_haiku_4_5 (the first prefer entry that satisfies every constraint), so set ANTHROPIC_API_KEY in a local .env before you apply. For $0 local testing, put ollama.llama_3_1_8b first in prefer instead.
2. Validate, plan, apply
# Parse, type-check, and catalog-check the source. No network, no writes.
aislang validate support-agent.ais
# Resolve models against the catalog, reconcile the lockfile, and print
# the Compose stack it would deploy (plus litellm_config.yaml + .env.example).
aislang plan support-agent.ais
# Stand up the stack and record its state.
aislang apply support-agent.ais
aislang apply brings up a Docker Compose stack: a LiteLLM router (the single endpoint all agents route model calls through), a Jaeger sidecar for tracing, and a budget counter that enforces your caps fail-closed. Provider API keys are never read by aiSlang itself — they come from a local .env that Docker Compose passes to the containers at runtime; plan emits a safe-to-commit .env.example template listing exactly which variables you need.
3. Chat and eval
# Open an interactive REPL against the deployed agent. The CLI spawns the
# declared MCP servers as stdio children for the duration of the session.
aislang chat support support-agent.ais
# Run the declared evals against the deployed agent and print a JSON report
# with per-case scores and Jaeger trace URLs. Exit 1 if any eval misses its threshold.
aislang eval support-agent.ais
4. Tear it down
# Stop and remove the containers and volumes. Idempotent — safe to re-run.
aislang destroy support-agent.ais
Next steps
- Language — the full
.aislanguage reference. - CLI reference — every subcommand and its flags.
The aiSlang language
An aiSlang project is a single .ais file (or a directory of them) that
describes a whole multi-agent system: its models, prompts, tools, agents,
knowledge, evals, budgets, and deploy target. The syntax is HCL-like — labeled
blocks, key = value assignments, and nested blocks — and every value is
typed, so mistakes are caught at aislang validate rather than at runtime.
This chapter is a practical guide. For the complete formal grammar and typed semantics, see the aiSlang language reference (
LANGUAGE-SPEC-v0.md).
Typed literals use suffixes: money is $50 / $0.001, sizes and token counts
are 32k / 8GB, durations are 5s / 2m, and percentages are 85%.
Comments start with # or //.
This page walks through each construct with a minimal snippet. For the exact
grammar and type rules, the bundled language specification is authoritative
(aislang mcp-serve also exposes it as the lang_spec tool).
project — the top-level container
Every file has exactly one project block. Its label is the project name and
is used as a prefix for generated service names, trace attributes, and the
local state directory. All other resources live inside it.
project "support-agent" {
# budget, model, prompt, agent, deploy … all go here
}
budget — spend caps
A required, single budget block declares the hard spend caps and an
optimization hint. Caps are enforced at runtime: hard_cap_per_run is enforced
by the LiteLLM adapter on each turn, and hard_cap_per_day by a sidecar
counter that persists the day's spend. Use the unlimited keyword for free
local models.
budget {
hard_cap_per_day = $50
hard_cap_per_run = $0.50
optimize_for = cost # cost | latency | quality (informational hint)
}
model — capability-based selection
A model block describes what you need, not which vendor. You declare
required capabilities and constraints; the resolver picks a concrete model from
the bundled catalog and builds a LiteLLM fallback chain.
model "fast" {
requires = [chat, tool_use] # capabilities the model must have
context_min = 32k # minimum context window (tokens)
cost_max = $0.001 per 1k_tokens # optional cost ceiling
prefer = [anthropic.claude_haiku_4_5, openai.gpt_4o_mini]
fallback = [ollama.llama_3_1_8b]
pin = anthropic.claude_haiku_4_5 # optional: must equal the resolved id
source = api # api (default) | subscription
}
requires— a set of capabilities such aschat,tool_use,vision,embedding,reasoning,structured_output.context_min/cost_max— filters applied against the catalog; models that don't satisfy them are dropped from consideration.prefer— an ordered preference list. The resolver walks it in declared order and picks the first entry that satisfies the constraints.fallback— used only when noprefercandidate satisfies the constraints; also emitted as LiteLLM's fallback chain.pin— if set, the resolved model must equal this id, otherwise validation fails. Pins keep a deployment reproducible.source—api(default) routes the model through LiteLLM with hard budget enforcement;subscriptionmarks a model whose inference runs on the host chat app's subscription tier (Claude Pro, Cursor Team, …). Subscription models are still resolved against the catalog, but they get no LiteLLM routing entry and their spend is reported rather than capped.
The chosen model and its fallback chain are written to aislang.lock so a plan
is deterministic given the same source, catalog, and lockfile.
prompt — versioned, hashable prompts
A prompt block points at a markdown file and pins a version. The file
contents are hashed at validate time and the hash lands in the lockfile, so a
prompt edit is visible in the next plan.
prompt "system" {
file = "./prompts/system.md"
version = "1.0.0"
template = true # optional: render as a MiniJinja template at chat time
}
With template = true, the file body is rendered as a
MiniJinja template against a built-in context
{ agent, prompt, model } (all strings). HTML auto-escaping is on; opt out per
value with the |safe filter. Without it, the file is sent to the model
verbatim.
mcp_server — tool servers
An mcp_server block declares an MCP tool server by container image. Secrets
are scoped: a server only receives the secrets it lists in its secrets field,
so credentials never leak across containers.
mcp_server "docs" {
image = "mcp/filesystem:1.0.2"
mount = "./data" # optional bind mount
env = { ROOT = "/data" } # plain env (never put secrets here)
secrets = [secret.docs_token] # only this server gets docs_token
}
secret — scoped credentials
A secret block names a credential and its source, and lists which resources
may receive it. aiSlang never reads real secret values into the plan — it emits
a safe .env.example template and hands the actual values to the runtime.
secret "docs_token" {
source = "env://DOCS_TOKEN" # env://VAR or file://path
scope = [mcp_server.docs] # agents and/or mcp_servers
}
env://VAR— read from a process environment variable at runtime.file://path— read the file contents as the secret value.scope— the set of agents and MCP servers allowed to see the secret. Referencing a secret from a resource outside its scope is a compile error.
agent — a deployed agent
An agent binds a model to a system prompt and, optionally, a set of tools and
knowledge stores.
agent "support" {
model = model.fast
system_prompt = prompt.system
tools = [mcp_server.docs] # optional MCP tool servers
knowledge = [vector_store.handbook] # optional RAG stores
skills = [skill.list_files] # optional named skills
}
RAG — embedding, vector_store, and agent.knowledge
Retrieval-augmented generation is expressed with two blocks plus an
agent.knowledge reference. An embedding block names the embedding model; a
vector_store block declares the corpus to index; and an agent attaches one or
more stores through knowledge.
embedding "small" {
provider = "openai" # openai | cohere | ollama
model = "text-embedding-3-small"
}
vector_store "handbook" {
embedding = embedding.small
source = "git:acme/handbook#docs" # path, URL, or git ref
chunk_size = 1k # 256..8192
chunk_overlap = 128 # optional, must be < chunk_size
}
agent "support" {
model = model.fast
system_prompt = prompt.system
knowledge = [vector_store.handbook]
}
The source string is disambiguated by prefix:
"./docs/"or"/abs/path"— a local file or directory."https://…"— a single URL fetch (no recursion)."crawl:https://…"— a recursive crawl from the seed URL."git:owner/repo#subpath"— a shallow clone of a GitHub repo; the optional#subpathnarrows indexing to a subdirectory.
Build and inspect indexes with aislang rag build / query / verify (see
CLI reference).
skill — named, typed operations
A skill block declares an operation an agent can invoke, either by shelling
out (exec) or by delegating to a tool on an mcp_server (mcp). Exactly one
of the two modes is required. output_type declares how the result is parsed.
# Shell-exec skill
skill "list_files" {
exec = "ls -1 ./data"
output_type = list_string # string | json | list_string
}
# MCP-delegate skill
skill "read_doc" {
mcp = mcp_server.docs.tool.read_file
output_type = json
}
exec— a shell command run in a sandboxed per-skill working directory with a scrubbed environment and a command denylist.mcp— a four-segment referencemcp_server.<server>.tool.<tool>. The server label is checked at validate time; the tool is resolved at runtime.output_type—string(verbatim),json(parsed), orlist_string(split on newlines).
Run a skill end-to-end with aislang skill run <label> <file>.
Composition — pipeline, loop, branch
Composition blocks describe how agents combine into a larger flow.
A pipeline runs agents in sequence; each step's output feeds the next.
pipeline "draft_and_review" {
steps = [agent.drafter, agent.reviewer]
input = "customer question" # optional descriptive metadata
output = "reviewed answer" # optional descriptive metadata
}
A loop re-runs a pipeline while a condition renders truthy, hard-capped by
max_iter (1..100) to guard against runaway loops.
loop "refine" {
body = pipeline.draft_and_review
condition = "{{ output.needs_more_work }}" # MiniJinja
max_iter = 8
}
A branch dispatches to one of several pipelines based on a rendered
value, with a required default.
branch "route" {
on = "{{ classifier_result }}" # MiniJinja
cases = {
"billing" = pipeline.billing_flow,
"tech" = pipeline.tech_flow,
}
default = pipeline.general_flow
}
Built-in orchestration patterns
The user-facing LLM that drives your deployed agents is declared as a main
block. Its pattern field names the relationship between that entry point and
the deployed agents. The six patterns are:
| Pattern | The entry point… |
|---|---|
orchestrator | plans the task, dispatches sub-tasks to agents in sequence, aggregates results. |
supervisor | monitors agent outputs, can intervene, makes final approve/reject calls. |
router | classifies input and routes to exactly one agent per turn. |
reflector | critiques agent outputs and produces a refined final answer. |
planner_executor | plans the steps and delegates execution to agents. |
coordinator | facilitates communication between peer agents. |
main "support_orchestrator" {
model = model.thinker
chat_client = claude_desktop # claude_desktop | cursor | chatgpt | open_webui | custom
pattern = orchestrator
system_prompt = prompt.system
}
A main block is never deployed itself — it describes the principal LLM's role
so an MCP host (Claude Desktop, Cursor, …) can act as the orchestrator with your
deployed agents as its tools. Pair it with source = subscription on its model
to run the orchestration on your existing subscription instead of the API.
eval — datasets, metrics, thresholds
An eval block scores an agent against a JSONL dataset with a metric and a pass
threshold.
eval "factual_accuracy" {
dataset = "./evals/faq.jsonl" # JSONL of {input, expected}
metric = exact_match # exact_match | f1
threshold = 85%
aggregator = mean # mean | min | max | percentile_p95
agent = agent.support # optional; inferred if unambiguous
}
Run with aislang eval <file>; the command exits non-zero if any eval falls
below its threshold.
deploy — target and exposure
A deploy block chooses the emit target and how the stack is exposed.
deploy "local" {
target = compose # compose | kubernetes
expose {
port = 8080 # agent-runtime port (functional under kubernetes)
web_ui = true # Open WebUI sidecar at http://localhost:3000
ingress = "support.example.com" # kubernetes only; requires web_ui = true
}
}
target = composeemits a Docker Compose stack.target = kubernetesemits raw manifests plus a Helm chart.expose { … }—portbinds the agent runtime,web_uiadds an Open WebUI browser-chat sidecar, andingress(Kubernetes only) fronts that UI at a host name. See Deploying stacks.
Multi-module projects
When one agent's resources should be reusable by another, split the project
across files. Put an aislang.toml at the project root with a [modules]
table, then reference sibling resources with use <module>.<label>.
# aislang.toml
[project]
name = "support-stack"
version = "0.1.0"
[modules]
architect = "agents/architect.ais"
coder = "agents/coder.ais"
Each .ais file still carries its own project block. A module's resources
live in the <module-name>. namespace, so a model "thinker" declared in
architect.ais is referenced elsewhere as use architect.thinker:
# agents/coder.ais
project "coder-module" {
budget { hard_cap_per_day = $1 hard_cap_per_run = $0.01 }
prompt "system" { file = "./system.md" version = "1.0.0" }
agent "coder" {
model = use architect.thinker # cross-module reference
system_prompt = prompt.system # same-module reference
tools = []
}
}
The commands that take a positional <file.ais> (validate, plan, apply,
destroy) also accept a project directory holding an aislang.toml — the CLI
autodetects which shape it was handed.
Next: the CLI reference for every command and flag, and Deploying stacks for Compose and Kubernetes targets.
CLI reference
The aislang binary is the single entry point for authoring, validating,
deploying, and operating aiSlang projects. Every command that takes a positional
<file> accepts either a single .ais file or a project directory containing
an aislang.toml (see multi-module projects),
except where noted.
Run aislang <command> --help for the exhaustive flag list. Verbosity flags
(-v, -vv, -vvv) accumulate and print progressively more detail to stderr.
validate
Parse, type-check, and resolve references in a project without touching the network or writing any files.
Flags
-v/-vv/-vvv— one-line resource counts / pretty-printed AST / full AST as JSON IR.
Exit codes — 0 when the source parses and type-checks; 1 on any parse,
type, or reference error (rendered with a source-aware snippet and caret).
aislang validate examples/support-agent.ais
# OK: examples/support-agent.ais parsed and type-checked
aislang validate examples/multi-module-demo/ # a project directory
plan
Resolve models, reconcile the lockfile, and emit deployment artifacts. The
Compose YAML (or Kubernetes manifests) prints to stdout; supporting files
(litellm_config.yaml, .env.example, aislang.lock) are written to disk and
their paths reported on stderr. No stack is brought up.
Flags
-o, --output-dir <DIR>— where to write generated artifacts. Defaults to the source file's directory.--no-lock— skip reading/writingaislang.lockand always re-resolve. Useful for a one-off plan that shouldn't touch the lockfile.--smoke-test— pipe the emitted YAML throughdocker compose -f - config -qto confirm Docker accepts the structure. Skips (exit 0) if Docker isn't on PATH.--smoke-test-live— bring the stack up withdocker compose up -d, poll LiteLLM on port 4000, fire one real/v1/chat/completionsagainst the first resolved model, then alwaysdown -v(even on failure). Implies--smoke-test. Requires Docker and a populated.envin the output directory.
aislang plan examples/support-agent.ais -o ./out
aislang plan examples/support-agent.ais --smoke-test
apply
Emit the stack and deploy it. Under target = compose it writes stack.yml
and runs docker compose up -d, then records .aislang/state.json. Under
target = kubernetes it writes manifests/ and chart/, then runs
helm upgrade --install <deploy-label> chart/. Both paths degrade gracefully
when their backend CLI is missing — the artifacts are written and the manual
command is printed.
aislang apply examples/support-agent.ais
# stack is up — next: aislang chat support examples/support-agent.ais
destroy
Tear down the deployed stack and free its resources. Under Compose it runs
docker compose down -v; under Kubernetes, helm uninstall <deploy-label>.
The operation is idempotent — re-running against an already-torn-down stack
exits 0 with a "nothing to destroy" message. The applied_at field in
.aislang/state.json is the source of truth.
Flags
--force— run the teardown even when the state file is absent or already marked torn down. Useful after manual edits to.aislang/.
aislang destroy examples/support-agent.ais
aislang destroy --force examples/support-agent.ais
eval
Run the project's declared eval blocks against the deployed agent and print a
JSON EvalReport to stdout. Each case carries a trace_url that deep-links
into Jaeger.
Flags
-e, --eval <LABEL>— run only the named eval. When omitted, everyevalblock runs.
Exit codes — 0 only when every eval meets its threshold; 1 otherwise.
aislang eval examples/support-agent.ais
aislang eval -e factual_accuracy examples/support-agent.ais
aislang eval examples/support-agent.ais | jq -r '.trace_url, .cases[].trace_url'
cost
Print today's UTC spend — total plus per-model breakdown — read from
~/.aislang/spend.json, compared against the budget caps declared in the
source. Read-only, no network, no Docker.
aislang cost examples/support-agent.ais
status
Render stack health from .aislang/state.json and docker compose ps,
optionally with spend sections appended.
Flags
--spend— append today's total spend and the day-cap utilization.--model-spend[=MODEL]— append the by-model spend breakdown; pass--model-spend=<id>(note the=) to filter to one model, or the bare flag for all.--main-spend— append the by-main-block breakdown (the principal-LLM side of the cost model). Entries whose model usessource = subscriptionrender assubscription (no API charge).
Exit codes
0— stack matches state (applied and healthy).1— drift: state missing,applied_atis null, orcompose psdisagrees.2— Docker unavailable when a stack check was attempted.
aislang status examples/support-agent.ais
aislang status --spend --model-spend examples/support-agent.ais
aislang status --model-spend=anthropic.claude_haiku_4_5 examples/support-agent.ais
chat
Open an interactive chat session against a deployed agent. The command validates the agent exists, resolves its model and system prompt, spawns the declared MCP servers as stdio children for the session, and connects to the deployed LiteLLM. The tool-use loop is hard-capped at 8 iterations per turn.
Arguments — <agent> label followed by <file>.
Flags
--interactive— for pipeline agents, pause between steps so you can hand-edit the previous step's persisted output before the next step reads it. Every step's reply is persisted under<project>/.aislang/pipeline/<label>/regardless. No-op for single-agent and router paths.--keep-history— keep the previous session's pipeline persistence directory instead of wiping it at session start.
aislang chat support examples/support-agent.ais
# type messages; /exit or Ctrl-D to leave
mcp-serve
Start the aiSlang MCP server, exposing the CLI surface (lang_spec,
examples, describe, validate, plan, apply, destroy, status,
chat_turn, eval) as MCP tools so MCP-aware hosts can drive aiSlang without
using the terminal. See MCP integration.
Flags
--stdio— use the stdio transport (default). This is what desktop hosts spawn as a child process. Mutually exclusive with--http.--http <ADDR>— serve the same tools over Streamable-HTTP athttp://<ADDR>/mcp. Accepts anyhost:port(0.0.0.0:8080,127.0.0.1:8080,[::1]:8080). Suitable for cluster/network deployments with multiple clients.--rag-build-dir <DIR>— build the project's RAG indexes before serving (equivalent torag build-all, tolerant of per-store failures). Used by the in-cluster agent runtime at startup.--litellm-url <URL>— the LiteLLM base URL the pre-serve RAG build embeds against.--wait-litellm-secs <SECS>— seconds to wait for LiteLLM to accept connections before the pre-serve build (0= don't wait). Only used with--rag-build-dir.
aislang mcp-serve --stdio
aislang mcp-serve --http 0.0.0.0:8080
skill run
Run one declared skill block end-to-end, giving .ais authors a way to test
skills without writing Rust glue. Shell-exec skills run through the per-skill
sandbox (scrubbed PATH, command denylist, per-skill working directory).
MCP-delegate skills spawn the referenced mcp_server container via
docker run -i, route the call, then shut the child down. The typed skill
output is printed to stdout.
Arguments — <skill-label> and <file> (single-file or project dir).
Flags
--args <JSON>— JSON arguments for MCP-delegate skills (e.g.{"path":"/data/hello.txt"}). Ignored for shell-exec skills. Defaults to{}.
Exit codes — 0 on success; 1 with the rendered error on any skill
failure (denylist match, non-zero exit, MCP failure).
aislang skill run list_files examples/support-agent.ais
aislang skill run read_doc examples/support-agent.ais --args '{"path":"/data/hello.txt"}'
rag
RAG runtime operations against declared vector_store blocks. All subcommands
take a <file> or project directory and default the LiteLLM proxy URL to
http://localhost:4000.
Subcommands
rag build <label> <file>— build one vector store's index from itssourceURI, embed every chunk via the referencedembeddingblock, and persist to<project>/.aislang/vector/<label>.bin.rag query <label> <file> <query>— embed the query and return the top-K matching chunks (default 3, override with--top-k <N>) by cosine similarity. Builds the index on first call if it doesn't exist yet.rag verify <file>— scan every declared vector store and report chunk counts per persisted index. Exits non-zero if any index is empty or missing.rag build-all <file>— build every declared vector store in one go, tolerating per-store failures (logged; exits 0 so a stack can still come up). Indexes land inAISLANG_VECTOR_DIRif set, else the project's.aislang/.
Flags — --litellm-url <URL> (all subcommands), --top-k <N> (query).
aislang rag build handbook examples/support-agent.ais
aislang rag query handbook examples/support-agent.ais "what is the return policy?"
aislang rag verify examples/support-agent.ais
aislang rag build-all examples/support-agent.ais
remote
Delegate a CLI verb to a managed control plane instead of running it locally.
It is the same aislang binary self-hosted users run — only --target
differs. The control plane itself is aiSlang Cloud, a separate managed
service out of scope for these local, self-hosted docs; this command is simply
the client that talks to it. Verbs operate on a project by its server-stamped
id.
Flags
--target <URL>— control-plane base URL (e.g.https://api.example.com).--tenant <SLUG>— tenant slug; defaults toAISLANG_TENANT, elsedefault.--token <TOKEN>— bearer token; defaults toAISLANG_CLOUD_TOKEN.
Subcommands — validate, plan, apply, destroy, status, each taking
a project id and delegating the same-named operation to the control plane.
export AISLANG_CLOUD_TOKEN=…
aislang remote --target=https://api.example.com --tenant=acme validate p-abc123
aislang remote --target=https://api.example.com --tenant=acme apply p-abc123
Deploying stacks
aislang is a compile-then-deploy tool: a single .ais source describes your
whole system — models, agents, prompts, MCP servers, evals, budgets — and
aislang apply turns it into a running stack. The deploy block picks the
target. This chapter covers the two self-hosted targets: Docker Compose
(the default) and Kubernetes (Helm).
Docker Compose (default)
With target = compose, aislang apply emits a stack.yml and brings it up
with docker compose up -d. The stack is a small, fixed set of services:
- A LiteLLM router — the central model gateway. Every agent's API calls go
through it, which is where model routing, fallback, and per-model config
live. Published to the host on
localhost:4000. - A Jaeger sidecar — receives OpenTelemetry traces from
chatandeval; UI atlocalhost:16686. - A budget-enforcement counter — accounts per-call spend against the caps
declared in your
budgetblock. - Your agents — the deployed agents declared in the source.
MCP servers are not long-running Compose services. They are spawned as
stdio child processes by aislang chat for the duration of a session, then
torn down when the session ends.
Provider credentials never enter the emitted artifacts. aislang plan writes
only a safe .env.example template (safe to commit) listing the env vars
LiteLLM expects — for example ANTHROPIC_API_KEY for an Anthropic primary or
OLLAMA_API_BASE for an Ollama primary. You copy it to a local .env, fill in
real values, and docker compose loads it at runtime.
deploy "local" {
target = compose
}
# Copy the emitted template and fill in your keys.
cp .env.example .env
# edit .env — add ANTHROPIC_API_KEY, etc.
# Stand up the stack (LiteLLM + Jaeger + budget counter + agents) in Docker.
aislang apply examples/support-agent.ais
# Chat with a deployed agent. `aislang chat` runs the tool-use loop in-CLI,
# talking to the Compose-managed LiteLLM at localhost:4000 and spawning the
# declared MCP servers as stdio children for the session.
aislang chat support examples/support-agent.ais
# When you're done, tear it all down (containers + volumes).
aislang destroy examples/support-agent.ais
Browser chat via Open WebUI
For users without an MCP-aware client, add web_ui = true to your deploy
block's expose { … } to bring up an Open WebUI
sidecar wired to the in-stack LiteLLM:
deploy "local" {
target = compose
expose {
web_ui = true # → http://localhost:3000 after `aislang apply`
}
}
After aislang apply, browse to http://localhost:3000, sign up (local
SQLite — the first user becomes admin), and pick any model alias from the
dropdown. Every model LiteLLM is configured to route appears there — handy for
confirming an agent's chosen model actually replies without writing any client
code.
aislang destroy runs docker compose down -v, which wipes the
openwebui-data volume — chat history and local accounts go away with the rest
of the stack.
Kubernetes (Helm)
Flip target = kubernetes and aislang apply packages the same stack as a
Helm chart and installs it with helm upgrade --install.
deploy "prod" {
target = kubernetes
expose {
web_ui = true # → Open WebUI Service (ClusterIP)
ingress = "support.example.com" # opt-in Ingress; omit for port-forward
}
}
Prerequisites
You need docker, kubectl, and helm. For a local cluster,
kind (Kubernetes in Docker) gives you a working
cluster in about 30 seconds:
# Docker — required prerequisite (same as target = compose).
sudo apt update && sudo apt install -y docker.io
sudo usermod -aG docker $USER && newgrp docker
# kubectl — the Kubernetes CLI.
curl -sSL -o /tmp/kubectl "https://dl.k8s.io/release/$(curl -sSL https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
sudo install -m 0755 /tmp/kubectl /usr/local/bin/kubectl
# helm — chart installer.
curl -sSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
# kind — local cluster.
curl -sSL -o /tmp/kind https://kind.sigs.k8s.io/dl/v0.24.0/kind-linux-amd64
sudo install -m 0755 /tmp/kind /usr/local/bin/kind
# Spin up a cluster and sanity-check it.
kind create cluster
kubectl get nodes
On macOS, swap the apt install step for brew install docker kubectl helm kind and make sure Docker Desktop is running.
Provider credentials
aislang never writes secret bodies into the chart values. You create the
provider-creds Secret yourself; the chart references it via
envFrom: secretRef: aislang-providers:
kubectl create secret generic aislang-providers \
--from-literal=ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY" \
--from-literal=OPENAI_API_KEY="$OPENAI_API_KEY" \
--from-literal=OLLAMA_API_BASE="$OLLAMA_API_BASE"
Plan, apply, port-forward, destroy
# Plan — writes manifests/ + a Helm chart/ to disk, but installs nothing.
aislang plan examples/support-agent-k8s.ais
# Apply — runs `helm upgrade --install <deploy-label> chart/`.
aislang apply examples/support-agent-k8s.ais
Open WebUI is a ClusterIP Service by default — reachable only from inside the
cluster. Either port-forward it (works on any cluster, no Ingress controller
needed) or set ingress = "<host>" in your source and point DNS at your
cluster's Ingress controller:
kubectl port-forward svc/openwebui 3000:8080
aislang chat runs from your machine and dials LiteLLM at a fixed
localhost:4000. Under Kubernetes, LiteLLM is a ClusterIP Service, so
port-forward it in a second terminal before chatting:
kubectl port-forward svc/litellm 4000:4000
aislang chat support examples/support-agent-k8s.ais
Tear it down with either command — both are idempotent:
aislang destroy examples/support-agent-k8s.ais # runs `helm uninstall <deploy-label>`
# or directly:
helm uninstall <deploy-label>
Overriding chart values
The chart exposes a small set of install-time overrides you can apply without
re-running aislang plan:
helm upgrade --install prod ./chart \
--set litellm.replicas=3 \
--set openwebui.persistence.size=5Gi \
--set ingress.host=support.example.com
MCP integration
aislang mcp-serve exposes the whole CLI surface as an
MCP server, so an MCP host — Claude
Desktop, Cursor, or any other MCP-aware LLM app — can author, deploy, and chat
with .ais agents without you touching a terminal. The tools mirror the CLI:
validate, plan, apply, destroy, status, chat_turn, eval, and
more.
Local (stdio) setup
Desktop hosts spawn aislang mcp-serve --stdio as a child process and talk to
it over stdin/stdout. Add the block below to your host's MCP config (a
ready-to-edit copy lives at
examples/claude_desktop_config.json):
{
"mcpServers": {
"aislang": {
"command": "/absolute/path/to/aislang",
"args": ["mcp-serve", "--stdio"],
"env": {
"AISLANG_LITELLM_URL": "http://127.0.0.1:4000"
}
}
}
}
Config file location
For Claude Desktop, the config file lives at:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Linux:
~/.config/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
Other MCP hosts (Cursor, etc.) accept the same mcpServers block in their own
config location.
Use an absolute command path
This is the most common setup mistake. GUI apps don't inherit your shell
PATH, so a bare "aislang" almost always fails with "server
disconnected". Point command at the real binary — for example
~/.local/bin/aislang after an install, or target/release/aislang (or
target/debug/aislang) in a source checkout. Find the absolute path with:
command -v aislang # if it's on your PATH
realpath target/release/aislang # in a source checkout
Environment and working directory
The env block is optional. The authoring tools (validate, plan,
describe, lang_spec) need nothing. Only the conversational tools
(chat_turn, eval) talk to a running LiteLLM — set AISLANG_LITELLM_URL
only if it isn't the http://127.0.0.1:4000 default. Provider keys live in
LiteLLM, never in this config.
Because the host launches the server with an arbitrary working directory, pass
absolute source_paths to the tools (e.g.
/home/you/project/support-agent.ais, not support-agent.ais).
After editing the config, restart the host. The aislang tools then appear in
its tool list — try asking it to validate, apply, or run a chat turn against
one of your .ais files.
HTTP transport
For network or remote MCP clients (in-cluster deployments, custom clients), serve the same toolset over HTTP:
aislang mcp-serve --http 0.0.0.0:8080
The tools are then mounted at http://host:8080/mcp.
Observability
Every aislang chat and aislang eval run is observable in two ways: it emits
OpenTelemetry traces you can inspect in Jaeger, and it accumulates per-call
cost into a local spend ledger checked against your declared budget caps.
Tracing
Every aislang chat and aislang eval invocation emits OpenTelemetry traces
(following the OTel-GenAI semantic
conventions) over
OTLP/HTTP to the bundled Jaeger sidecar, whose UI is at
http://localhost:16686.
The trace tree mirrors the chat loop's structure:
- Each user turn is one span.
- The tool-use loop fans out into one span per iteration.
- Each iteration carries one LLM-call span plus zero-or-more MCP tool-call spans.
Span attributes follow the OTel-GenAI conventions in the gen_ai.* namespace
(gen_ai.system, gen_ai.request.model, gen_ai.usage.input_tokens, …), plus
an mcp.* namespace for tool calls. The mcp.* attributes record sizes
only, never argument contents — PII-safe by construction.
After each command the trace URL prints to stderr — paste it into a browser to see the full call hierarchy:
trace (eval `factual_accuracy`): http://localhost:16686/trace/7ca996386817d4859e6921c26245e4e3
aislang eval's JSON report carries the same URLs as machine-readable fields,
so CI artifacts can deep-link straight into Jaeger per case:
aislang eval examples/support-agent.ais | jq -r '.trace_url, .cases[].trace_url'
Override the Jaeger UI URL with AISLANG_JAEGER_UI (e.g. for a shared
collector), or the OTLP endpoint with the standard
OTEL_EXPORTER_OTLP_ENDPOINT env var to ship to an external collector
(Honeycomb, Grafana Tempo, Datadog, an OTel Collector, …).
Spend tracking
aislang chat and aislang eval accumulate per-call cost into
~/.aislang/spend.json. The file is written atomically and keyed by project +
UTC date, so a day's spend is always a consistent snapshot.
aislang cost <file> prints today's total plus a per-model breakdown, compared
against the budget caps declared in the source:
aislang cost examples/support-agent.ais
aislang status surfaces the same figures inline:
aislang status --spend examples/support-agent.ais # today's total + cap utilization
aislang status --model-spend examples/support-agent.ais # per-model breakdown
Budget caps are enforced at runtime, fail-closed: a turn that would push spend past a cap is stopped before it runs.
budget {
hard_cap_per_run = $0.50 # a single run may not exceed this
hard_cap_per_day = $50 # today's total may not exceed this
}
Note: Open WebUI chats talk to LiteLLM directly and therefore bypass this tracker — their cost is not recorded in
~/.aislang/spend.json. Useaislang chat/aislang evalwhen you need spend accounting and cap enforcement.