Skip to content
Merged
Show file tree
Hide file tree
Changes from 42 commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
267cebf
design doc WIP
aaronvg Aug 1, 2026
7d698fd
BEP draft: AI functions and agents — docs, working reference, notes
aaronvg Aug 3, 2026
5317dbb
bep try again
aaronvg Aug 3, 2026
da937a5
add more files
aaronvg Aug 5, 2026
3dbd98d
new bep
aaronvg Aug 7, 2026
ef03373
second bep
aaronvg Aug 7, 2026
b48041c
claude code
aaronvg Aug 7, 2026
d49b029
Merge remote-tracking branch 'origin/canary' into aaron/custom-llm-pr…
aaronvg Aug 7, 2026
4c9bc5e
mcp stuff
aaronvg Aug 7, 2026
89db5de
some publish thing
aaronvg Aug 9, 2026
95b2bcc
clean up slop
aaronvg Aug 9, 2026
15e7bbc
feat(ai): builtin ai/provider packages + @spec desugar for LLM functions
aaronvg Aug 9, 2026
fac38ce
test(ai): accept snapshot churn from builtin packages; fmt tools field
aaronvg Aug 9, 2026
e587133
fix(syntax): ClientField::value() returns the full unquoted shorthand
aaronvg Aug 9, 2026
2f64f97
Merge remote-tracking branch 'origin/canary' into aaron/custom-llm-pr…
aaronvg Aug 9, 2026
d6c9f89
ci: fix PR 4352 gate failures — java Object-method escape, baselines
aaronvg Aug 9, 2026
cbba09c
chore(clippy): use method path over redundant closure in tools lowering
aaronvg Aug 9, 2026
180b90a
ci: human-readable size-gate baselines; trim whitespace in concepts page
aaronvg Aug 9, 2026
bfabceb
chore(clippy): box FunctionDeclBody::Llm after tools field growth
aaronvg Aug 9, 2026
d04a16b
ci: bump packed-program size ceilings for the builtin ai stdlib
aaronvg Aug 9, 2026
955cacb
fix(ai): address multi-agent review findings on the @spec desugar
aaronvg Aug 9, 2026
ad8ac3a
chore(clippy): hoist the fill helper above statements in LlmFunctionBody
aaronvg Aug 9, 2026
8328d57
feat(ai): pure client construction, RoundRobin wrapper, mcp -> ai.mcp
aaronvg Aug 9, 2026
7c33919
feat(ai)!: single-path LLM functions — every function runs the Agent …
aaronvg Aug 9, 2026
d7e10f3
test/fmt: single-path follow-ups — AST tests, fmt client expressions
aaronvg Aug 10, 2026
ee15fee
feat(ai): streaming — StreamingClient, ai.Stream, $stream companion
aaronvg Aug 10, 2026
becb35a
fix(codegen)!: keep LLM functions in generated SDKs; finish fixture m…
aaronvg Aug 10, 2026
38382ad
refactor(ast)!: delete the legacy client/retry/LLM-call synthesis
aaronvg Aug 10, 2026
deca27c
feat(ai)!: host-language streaming — $stream returns baml.llm.Stream
aaronvg Aug 10, 2026
136d817
test: regenerate snapshots for the single-path stream type
aaronvg Aug 10, 2026
e2e46de
fix(sdk-tests): restore BAML stream collectors; go $parse surface; fmt
aaronvg Aug 10, 2026
055a76d
fix(openai): resolve api_key/base_url env at request time by NAME
aaronvg Aug 10, 2026
6258927
test(sdk): re-record streaming SSE fixtures in Responses format
aaronvg Aug 10, 2026
0e9dea2
fix(ci): stale LSP hover expectation; drop orphaned snapshot
aaronvg Aug 10, 2026
6da6d0f
test: keep the stdlib out of user-project bytecode snapshots
aaronvg Aug 10, 2026
4a7b1f7
fix(ai): runner-level failures are not blind-replayable; unbreak carg…
aaronvg Aug 10, 2026
de8fbd5
fix: review findings — client(...) parse, Java accessors, SSE validator
aaronvg Aug 10, 2026
dfe0c45
chore: rustfmt + accept describe_command snapshots
aaronvg Aug 10, 2026
662c37e
fix(sdk_tests): revive the C# phase10 streaming fixture on the new path
aaronvg Aug 10, 2026
8809f0e
fix(lsp2): update the companion-listing test to the current companion…
aaronvg Aug 10, 2026
285fa1f
chore: drop the orphaned baml_src/mcp.snap
aaronvg Aug 10, 2026
732bdd8
Merge origin/canary into aaron/custom-llm-providers-v5
aaronvg Aug 10, 2026
9707f32
fix(_plan): make mount/unmount round-trip through the registry
aaronvg Aug 10, 2026
1a972ad
perf(sdkgen-ts): ship inlined bytecode as base64, not a decimal array
aaronvg Aug 10, 2026
2511cb7
chore: sort the base64 dependency
aaronvg Aug 10, 2026
412966b
Merge remote-tracking branch 'origin/canary' into aaron/custom-llm-pr…
aaronvg Aug 10, 2026
43a4157
Merge origin/canary; regenerate snapshots for the rvalue-sinking fix
aaronvg Aug 10, 2026
ec5b331
refactor(ai): give the ai package the namespace tree the BEP specifies
aaronvg Aug 10, 2026
e9d48d5
fix: two probes that the ai namespace move invalidated
aaronvg Aug 10, 2026
b798e93
fix(lsp tests): migrate fixtures to the ai.clients namespace
aaronvg Aug 10, 2026
a415615
Fix streaming
aaronvg Aug 11, 2026
01ab40d
chore: keep plan drafts out of the PR
aaronvg Aug 11, 2026
b013a8d
fix: refresh generated stream migration fixtures
aaronvg Aug 11, 2026
f80c22f
fix: avoid generated prompt data accessor
aaronvg Aug 11, 2026
84699b8
more fixes
aaronvg Aug 11, 2026
1090caf
test: restore ai stream migration coverage
aaronvg Aug 11, 2026
4d09fd2
test: refresh bytecode snapshots
aaronvg Aug 11, 2026
ebaf0ab
fix errors
aaronvg Aug 11, 2026
06d01f9
test: refresh ai std snapshots
aaronvg Aug 11, 2026
938f2cb
ci: retry macos bridge test
aaronvg Aug 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
9 changes: 6 additions & 3 deletions baml_language/.cargo/size-gate.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,17 @@ max_file_bytes = "22.5 MiB"
kind = "pack"
policy.max_delta_pct = 3.0

# Bumped ~3% above the Aug 2026 baselines when the builtin ai/provider
# stdlib packages landed (PR #4352): the packed program carries their
# bytecode (~700 KB).
[artifacts.packed-program.platform.aarch64-apple-darwin]
max_file_bytes = "13.6 MiB"
max_file_bytes = "14.3 MiB"

[artifacts.packed-program.platform.x86_64-unknown-linux-gnu]
max_file_bytes = "17.4 MiB"
max_file_bytes = "18.3 MiB"

[artifacts.packed-program.platform.x86_64-pc-windows-msvc]
max_file_bytes = "14.5 MiB"
max_file_bytes = "15.3 MiB"

[artifacts.packed-program.pack]
cli_package = "baml_cli"
Expand Down
4 changes: 2 additions & 2 deletions baml_language/.ci/size-gate/aarch64-apple-darwin.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ stripped_bytes = "20.3 MiB"
gzip_bytes = "9.7 MiB"

[artifacts.packed-program]
file_bytes = "13.2 MiB"
gzip_bytes = "6.1 MiB"
file_bytes = "13.8 MiB"
gzip_bytes = "6.3 MiB"
4 changes: 2 additions & 2 deletions baml_language/.ci/size-gate/x86_64-pc-windows-msvc.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ stripped_bytes = "21.9 MiB"
gzip_bytes = "9.9 MiB"

[artifacts.packed-program]
file_bytes = "14.1 MiB"
gzip_bytes = "6.2 MiB"
file_bytes = "14.8 MiB"
gzip_bytes = "6.4 MiB"
4 changes: 2 additions & 2 deletions baml_language/.ci/size-gate/x86_64-unknown-linux-gnu.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ stripped_bytes = "26.1 MiB"
gzip_bytes = "11.1 MiB"

[artifacts.packed-program]
file_bytes = "16.9 MiB"
gzip_bytes = "7.0 MiB"
file_bytes = "17.7 MiB"
gzip_bytes = "7.2 MiB"
6 changes: 6 additions & 0 deletions baml_language/.markdown-whitelist
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ TYPE_SYSTEM.md
# Speedtest workload definitions
tools/speedtest/workloads/**/*.md

# BEP planning trees (design pages + working-reference notes)
_plan/*.md
_plan/**/*.md
_planv2/*.md
_planv2/**/*.md

# BEX event identity & program metadata: cross-team contract docs for the
# paulo/bex-event-identity-metadata work (design ticket, review, response,
# post-implementation notes) — REVIEW.md §7.8 asks for these to ship with
Expand Down
2 changes: 2 additions & 0 deletions baml_language/_plan/ai_agents/baml.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[package]
name = "ai_agents"
57 changes: 57 additions & 0 deletions baml_language/_plan/ai_agents/baml_src/ns_ai/client.baml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// A client is a stateless codec + transport for one provider wire format.
// Mirrors _plan/pages/02_guides/04_models.md.

class ProviderRequest {
url: string,
headers: map<string, string>,
body_json: string,
}

class ProviderResponse {
status: int,
body_json: string,
}

// Duties: render (journal -> provider-native request), invoke (one
// stateless call), ingest (response -> events, raw payload preserved).
// Clients render any event union but only ever PRODUCE built-in events.
interface Client {
type Ev = Event

function id(self) -> string throws never
function render(self, j: Journal<Self.Ev>, tb: Toolbox, output_schema: string) -> ProviderRequest throws unknown
function invoke(self, req: ProviderRequest) -> ProviderResponse throws unknown
function ingest(self, resp: ProviderResponse) -> Event[] throws unknown
}

//# A fake provider: drives the whole loop deterministically in tests.

// Mirrors _plan/pages/04_advanced/02_evals.md.
class ScriptedClient<X> {
turns: Event[][], // what "the model" does on call 1, 2, 3...
i: int,

implements Client {
type Ev = Event | X

function id(self) -> string {
"scripted"
}
function render(
self,
j: Journal<Event | X>,
tb: Toolbox,
output_schema: string,
) -> ProviderRequest {
ProviderRequest { url: "", headers: {}, body_json: "" }
}
function invoke(self, req: ProviderRequest) -> ProviderResponse {
ProviderResponse { status: 200, body_json: "" }
}
function ingest(self, resp: ProviderResponse) -> Event[] {
let batch: Event[] = self.turns.at(self.i) ?? [];
self.i += 1;
batch
}
}
}
22 changes: 22 additions & 0 deletions baml_language/_plan/ai_agents/baml_src/ns_ai/errors.baml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// User-defined session errors — the typed catalog from the BEP's
// errors-and-retries guide, expressed as ordinary classes.
// `throw X { ... }` + typed `catch` arms + `throws X` signatures all work.

class StepBudgetExceeded {
message: string,
steps: int,
}

class CostBudgetExceeded {
message: string,
spent_usd: float,
}

// Thrown by the async runner when a run() blocked on cancelled work is
// torn down (`baml.session.Interrupted` in the BEP's error catalog). The
// synchronous reference never throws it: an interrupt is recorded as an
// Interrupted event and the default policy lets the model react
// (04_steering.md); CancelAll only fires cancel tokens.
class SessionInterrupted {
reason: string,
}
216 changes: 216 additions & 0 deletions baml_language/_plan/ai_agents/baml_src/ns_ai/events.baml
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
// The journal: an append-only log of typed events. One per session.
// Mirrors _plan/pages/02_guides/10_journal.md.

class SessionStarted {
function_name: string,
args_json: string,
}

class UserMessage {
content: string,
}

class AssistantMessage {
content: string,
raw_json: string?,
provider: string,
}

class ToolRequested {
call_id: string,
tool: string,
args_json: string,
}

class ToolCompleted {
call_id: string,
result_json: string,
}

class ToolFailed {
call_id: string,
error: string,
}

class FinalProduced {
result_json: string,
}

class ToolsChanged {
names: string[],
}

class StepCompleted {
call_id: string,
step: string,
result_json: string,
}

class ChildSpawned {
child_id: string,
goal: string,
}

class ChildFinished {
child_id: string,
result_json: string,
}

class Interrupted {
reason: string,
}

// A setter changed the session's client (03_configuration.md). Journal-only.
class ClientChanged {
client_id: string,
}

// A setter changed the session's policy. Journal-only. Records that the
// change happened, not the value: a policy is a runtime value (often
// closing over state), so resume takes the policy from the caller and
// this event is audit history (see reference notes, spec issues).
// The literal field is a wire discriminant: an EMPTY class in a journaled
// union structurally matches any JSON object on resume and swallows every
// variant after it (see reference notes).
class PolicyChanged {
kind: "policy_changed",
}

class Usage {
input_tokens: int,
output_tokens: int,
}

class Compacted {
summary: string,
through_seq: int,
}

type Event = SessionStarted
| UserMessage
| AssistantMessage
| ToolRequested
| ToolCompleted
| ToolFailed
| FinalProduced
| ToolsChanged
| ClientChanged
| PolicyChanged
| StepCompleted
| ChildSpawned
| ChildFinished
| Interrupted
| Usage
| Compacted;

// Custom events are invisible to the model unless they implement this.
// Mirrors `baml.session.Promptable` in 11_journal.md.
interface Promptable {
function to_prompt(self) -> string? throws never
}

class Entry<E> {
seq: int,
event: E,
// true = written by a policy via record(): journal-only history that was
// never folded live, so refold skips it too (see refold in session.baml)
recorded: bool,
}

class Journal<E> {
entries: Entry<E>[],
replaying: bool, // set by refold; suppresses record()

// runner-side: append an event that IS folded through the policy
function append(self, e: E) -> void {
self.entries.push(Entry { seq: self.entries.length(), event: e, recorded: false });
}

// policy-side: record a journal-only decision (e.g. PermissionRequested).
// Never folded; suppressed during refold, so replay cannot duplicate it.
// This is the ONLY journal write allowed inside Policy.update.
function record(self, e: E) -> void {
if (!self.replaying) {
self.entries.push(Entry { seq: self.entries.length(), event: e, recorded: true });
}
}

function read_from(self, seq: int) -> Entry<E>[] {
// .filter() infers Entry<never>[] here (see reference notes); build manually
let out: Entry<E>[] = [];
for (let en in self.entries) {
if (en.seq >= seq) {
out.push(en);
}
}
out
}

function last_seq(self) -> int {
self.entries.length() - 1
}

function snapshot(self) -> string {
baml.json.to_string(self)
}
}

function new_journal<E>() -> Journal<E> {
Journal<E> { entries: [], replaying: false }
}

function resume_journal<E>(snap: string) -> Journal<E> {
baml.json.from_string<Journal<E>>(snap)
}

//# The transcript is a rendering of the journal, not storage.

// The real feature (${ctx.transcript}) lowers provider-natively; this
// library fold renders to text.
function render_transcript<E>(j: Journal<E>) -> string {
j.entries
.map((en) -> {
match (en.event) {
let s: SessionStarted => `[briefing] ${s.args_json}`,
let u: UserMessage => `[user] ${u.content}`,
let a: AssistantMessage => `[assistant] ${a.content}`,
let t: ToolRequested => `[calling] ${t.tool}(${t.args_json})`,
let t: ToolCompleted => `[result] ${t.result_json}`,
let t: ToolFailed => `[tool error] ${t.error}`,
let c: Compacted => `[earlier, summarized] ${c.summary}`,
let p: Promptable => p.to_prompt() ?? "", // custom events opt in
_ => "",
}
})
.filter((s) -> {
s != ""
})
.join("\n")
}

// Event factories — the library form of `baml.session.user(...)`.
// Also the workaround for qualified struct literals inside array literals
// (see _plan/reference_notes.md).
function user(content: string) -> Event {
UserMessage { content: content }
}

function assistant(content: string, provider: string = "test", raw_json: string? = null) -> Event {
AssistantMessage { content: content, raw_json: raw_json, provider: provider }
}

function tool_requested(call_id: string, tool: string, args_json: string) -> Event {
ToolRequested { call_id: call_id, tool: tool, args_json: args_json }
}

function tool_completed(call_id: string, result_json: string) -> Event {
ToolCompleted { call_id: call_id, result_json: result_json }
}

function final_produced(result_json: string) -> Event {
FinalProduced { result_json: result_json }
}

function usage_of(input_tokens: int, output_tokens: int) -> Event {
Usage { input_tokens: input_tokens, output_tokens: output_tokens }
}
Loading
Loading