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, with fallback chains 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 .ais source, 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 aislang binary and its prerequisites.
  • Quickstart — a running multi-agent stack in five minutes.
  • Language — the .ais language 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

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 as chat, 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 no prefer candidate 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.
  • sourceapi (default) routes the model through LiteLLM with hard budget enforcement; subscription marks 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 #subpath narrows 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 reference mcp_server.<server>.tool.<tool>. The server label is checked at validate time; the tool is resolved at runtime.
  • output_typestring (verbatim), json (parsed), or list_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:

PatternThe entry point…
orchestratorplans the task, dispatches sub-tasks to agents in sequence, aggregates results.
supervisormonitors agent outputs, can intervene, makes final approve/reject calls.
routerclassifies input and routes to exactly one agent per turn.
reflectorcritiques agent outputs and produces a refined final answer.
planner_executorplans the steps and delegates execution to agents.
coordinatorfacilitates 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 = compose emits a Docker Compose stack.
  • target = kubernetes emits raw manifests plus a Helm chart.
  • expose { … }port binds the agent runtime, web_ui adds an Open WebUI browser-chat sidecar, and ingress (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 codes0 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/writing aislang.lock and always re-resolve. Useful for a one-off plan that shouldn't touch the lockfile.
  • --smoke-test — pipe the emitted YAML through docker compose -f - config -q to confirm Docker accepts the structure. Skips (exit 0) if Docker isn't on PATH.
  • --smoke-test-live — bring the stack up with docker compose up -d, poll LiteLLM on port 4000, fire one real /v1/chat/completions against the first resolved model, then always down -v (even on failure). Implies --smoke-test. Requires Docker and a populated .env in 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, every eval block runs.

Exit codes0 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 uses source = subscription render as subscription (no API charge).

Exit codes

  • 0 — stack matches state (applied and healthy).
  • 1 — drift: state missing, applied_at is null, or compose ps disagrees.
  • 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 at http://<ADDR>/mcp. Accepts any host: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 to rag 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 codes0 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 its source URI, embed every chunk via the referenced embedding block, 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 in AISLANG_VECTOR_DIR if 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 to AISLANG_TENANT, else default.
  • --token <TOKEN> — bearer token; defaults to AISLANG_CLOUD_TOKEN.

Subcommandsvalidate, 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 chat and eval; UI at localhost:16686.
  • A budget-enforcement counter — accounts per-call spend against the caps declared in your budget block.
  • 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. Use aislang chat / aislang eval when you need spend accounting and cap enforcement.