Appearance
Getting started
This page scaffolds a project, adds one pipeline, generates it, and serves it. Every CLI command on this page, and every error message, was run against @aid-framework/cli 0.1.48.
You need Node.js 24 or newer. Nothing has to be cloned; the CLI is published.
1. Scaffold a project
bash
npx @aid-framework/cli init my-app
cd my-appThe command writes two files and stops:
console
created app/app.spec.yaml
created README.md
next: aid validate, then aid genapp/app.spec.yaml is the source of truth. README.md is a note to your future self about which directory the generator owns.
2. Look at the starter spec
yaml
specVersion: "0.1"
project:
name: my-app
targets:
- py-fastapiThat is deliberately the smallest legal spec. init writes specVersion: "0.1" - the original surface - and you raise it when you use a later construct:
| Version | Adds |
|---|---|
0.1 | The original surface: types, models, prompts, tools, stores, retrievers, pipelines, evals. |
0.2 | The closed agents, guardrails, memory and deployments sections. |
0.3 | models[].cache - prompt and semantic caching. |
0.4 | deployments[].image - the registry and repository a container image is published to. |
0.5 | pipelines[].trigger.manual and deployments[].pipeline - the pair that lets a target: batch deployment run one pipeline once to completion. |
0.6 | The google provider (Vertex AI). |
0.7 | A guardrails entry can name a decision (decision + onDecision). |
0.8 | The decision eval metric kind, which scores a prediction with a score decision. |
0.9 | The route pipeline step, which runs the arm a choice decision's answer names. |
0.10 | The gemini provider, which calls the Gemini API with an API key. |
A section or construct that needs a later version is reported as spec/not-supported with the version it needs, so you get told rather than silently ignored. Raise the value yourself; init does not guess.
3. Validate
bash
npx @aid-framework/cli validateconsole
app/app.spec.yaml: ok (0 errors, 0 warnings)validate parses the spec, lowers it to the IR, and runs every build-time gate. It writes nothing. It exits 0 on success, 1 on a failed gate, and 2 on a usage error - the full set is in the CLI reference.
4. Add a pipeline
Replace the contents of app/app.spec.yaml with this. It declares one model, one prompt, one input guardrail, one HTTP pipeline and one emitted type.
yaml
specVersion: "0.2"
project:
name: my-app
targets:
- py-fastapi
types:
Reply:
kind: struct
fields:
text:
type: string
required: true
models:
fast:
provider: openai
modelId: gpt-4o-mini
apiKeyEnv: OPENAI_API_KEY
capabilities: [json-schema]
params:
temperature: 0.2
maxTokens: 256
costProfile: openai-gpt-4o-mini
limits:
maxInputTokens: 8192
maxOutputTokens: 256
prompts:
greet:
version: 1
model: fast
template: |
Reply to this customer message in one sentence.
Message: {{ message }}
Answer with a JSON object holding exactly one field:
* text: the reply to send
variables:
message:
type: string
required: true
output:
kind: structured
schema: Reply
guardrails:
message_injection:
stages: [input]
policies: [prompt-injection]
action: block
onFail:
message: The message looked like an attempt to override the assistant.
log: true
pipelines:
hello:
description: Reply to one message.
trigger:
http:
method: post
path: /hello
steps:
- generate:
prompt: greet
input:
message: "{{ event.message }}"
as: reply
- emit:
event: ReplyThree parts of that are load-bearing and worth naming before the gates complain about them.
No guardrails block means no validation. The security gate refuses a POST trigger that passes caller-supplied text to a prompt when the document declares no guardrail with an input stage:
console
app/app.spec.yaml: error: the security gate rejected this endpoint: the POST /hello trigger
passes caller-supplied text to prompt "greet", but the specification declares no guardrail
with an "input" stage [ir/security-input-guardrail]
at pipelines.hello.steps[0]
hint: declare a guardrail with stages: [input]That is not a lint. It is the framework's central claim enforced at build time, and it is the reason a minimal spec cannot quietly ship an unprotected endpoint.
A pipeline needs a final emit. emit names the type the HTTP response is built from, and the route is generated from it. Leaving it out is not caught by validate - gen reports it as an internal error and exits 2:
console
aid: internal error in `gen`: internal: an emit step for pipeline 'hello' is missing after
validation; this is a generator bugTreat that message as "add the emit step". It is a sharp edge, and it is recorded here because a reader will meet it.
An uncovered HTTP pipeline warns. Validating a pipeline that no eval targets still succeeds, with one warning:
console
warning: the eval-coverage gate flagged this pipeline: "hello" is served over HTTP but no
eval with gate.ci: true targets it [ir/eval-uncovered]Warnings do not fail the command. Turning the warning into an eval is step 6 of the support-copilot walkthrough.
5. Generate
bash
npx @aid-framework/cli genconsole
py-fastapi: 20 created, 0 updated, 0 unchanged, 0 kept
created:
app/business/__init__.py
app/generated/__init__.py
app/generated/contracts/__init__.py
app/generated/deps.py
...(with the full list elided; the command prints all twenty). Running it a second time with no spec change reports 17 unchanged and 3 kept: the three seeded files under app/business/ belong to you, so the generator leaves them alone instead of counting them unchanged. The tree, the ownership rule and the manifest are described in The generated tree.
6. Serve it
The generated routers import aid-runtime-python. The ASGI entry point is yours - a few lines that mount the generated router:
python
from fastapi import FastAPI
from app.generated.routes.hello import hello_router
app = FastAPI()
app.include_router(hello_router)bash
python -m venv .venv
.venv/bin/pip install aid-runtime-python "fastapi>=0.115,<1" "uvicorn>=0.30,<1"
.venv/bin/uvicorn app.main:app --reloadbash
curl -sS -X POST localhost:8000/hello \
-H 'content-type: application/json' \
-d '{"message": "I was charged twice this month"}'json
{"text": "I am sorry about the duplicate charge - I can look into it now."}This request needs OPENAI_API_KEY in the environment, because fast is a real provider.
The provider: fake model that the examples use is a test double, not a no-key deployment option: it is a seeded simulation that deliberately answers correctly only a fraction of the time, so a prompt declaring output.kind: structured against it fails with StructuredOutputError after three attempts rather than producing an answer. The examples run offline because they inject a scripted fake through the generated setDeps seam, which is what Serving an app covers.
Where to go next
- Writing a spec - the surface, in the order you will reach for it.
- The generated tree - what each generated file is for, and what you are allowed to edit.
- Concepts - spec, IR, generator, runtime: why there are four layers.
- CLI reference - every command, flag and exit code.