This package is developed in Bolt Foundry’s monorepo and mirrored into
github.com/bolt-foundry/gambit. For now, that repo is a strict mirror (please
do not land direct changes there).
CI runs .github/workflows/gambit-mirror.yml on every main push that touches
this package, using scripts/sync-gambit-mirror.sh to open and auto-merge a PR
from the bft-codebot fork into the public mirror.
Gambit helps developers build the most accurate LLM apps by making it simple to provide exactly the right amount of context at the right time.
- Most teams wire one long prompt to several tools and hope the model routes correctly.
- Context often arrives as a single giant fetch or RAG blob, so costs climb and hallucinations slip in.
- Input/outputs are rarely typed, which makes orchestration brittle and hard to test offline.
- Debugging leans on provider logs instead of local traces, so reproducing failures is slow.
- Treat each step as a small deck with explicit inputs/outputs and guardrails; model calls are just one kind of action.
- Mix LLM and compute tasks interchangeably and effortlessly inside the same deck tree.
- Feed models only what they need per step; inject references and cards instead of dumping every document.
- Keep orchestration logic local and testable; run decks offline with predictable traces.
- Ship with built-in observability (streaming, REPL, debug UI) so debugging feels like regular software, not guesswork.
Requirements: Deno 2.2+ and OPENROUTER_API_KEY (set OPENROUTER_BASE_URL if
you proxy OpenRouter-style APIs).
Run the CLI directly from JSR (no install):
export OPENROUTER_API_KEY=...
deno run -A jsr:@bolt-foundry/gambit/cli --helpRun a packaged example without cloning:
export OPENROUTER_API_KEY=...
deno run -A jsr:@bolt-foundry/gambit/cli run --example hello_world.deck.md --init '"hi"'Run the built-in assistant (from a clone of this repo):
export OPENROUTER_API_KEY=...
deno run -A src/cli.ts run src/decks/gambit-assistant.deck.md --init '"hi"' --streamTalk to it in a REPL (default deck is src/decks/gambit-assistant.deck.md):
deno run -A src/cli.ts repl --message '"hello"' --stream --verboseOpen the debug UI:
deno run -A src/cli.ts serve src/decks/gambit-assistant.deck.md --port 8000
open http://localhost:8000/debugInstall the CLI once (uses the published JSR package):
deno install -A -n gambit jsr:@bolt-foundry/gambit/cli
gambit run path/to/root.deck.ts --init '"hi"'If you run from a remote URL (e.g., jsr:@bolt-foundry/gambit/cli), pass an
explicit deck path; the default REPL deck only exists in a local checkout.
Minimal Markdown deck (model-powered):
+++
label = "hello_world"
[modelParams]
model = "openai/gpt-4o-mini"
temperature = 0
+++
You are a concise assistant. Greet the user and echo the input.Run it:
deno run -A src/cli.ts run ./hello_world.deck.md --init '"Gambit"' --streamCompute deck in TypeScript (no model call):
// echo.deck.ts
import { defineDeck } from "jsr:@bolt-foundry/gambit";
import { z } from "zod";
export default defineDeck({
label: "echo",
inputSchema: z.object({ text: z.string() }),
outputSchema: z.object({ text: z.string(), length: z.number() }),
run(ctx) {
return { text: ctx.input.text, length: ctx.input.text.length };
},
});Run it:
deno run -A src/cli.ts run ./echo.deck.ts --init '{"text":"ping"}'Deck with a child action (calls a TypeScript tool):
+++
label = "agent_with_time"
modelParams = { model = "openai/gpt-4o-mini", temperature = 0 }
actionDecks = [
{ name = "get_time", path = "./get_time.deck.ts", description = "Return the current ISO timestamp." },
]
+++
A tiny agent that calls get_time, then replies with the timestamp and the input.And the child action:
// get_time.deck.ts
import { defineDeck } from "jsr:@bolt-foundry/gambit";
import { z } from "zod";
export default defineDeck({
label: "get_time",
inputSchema: z.object({}), // no args
outputSchema: z.object({ iso: z.string() }),
run() {
return { iso: new Date().toISOString() };
},
});- CLI entry:
src/cli.ts; runtime:src/runtime.ts; definitions:mod.ts. - Examples:
examples/hello_world.deck.md,examples/agent_with_multi_actions/. - Debug UI assets:
src/server.ts(built-in UI now renders schema-driven init forms beneath the user message box with a raw JSON tab, reconnect button, and a trace-formatting hook). - Tests/lint/format:
deno task test,deno task lint,deno task fmt; compile binary:deno task compile. - Docs index:
docs/README.md; authoring guide:docs/authoring.md; prompting notes:docs/hourglass.md; changelog:CHANGELOG.md.
- Authoring decks/cards:
docs/authoring.md - Runtime/guardrails:
docs/runtime.md - CLI, REPL, debug UI:
docs/cli.md - Examples guide:
docs/examples.md - OpenAI Chat Completions compatibility:
docs/openai-compat.md
If you already construct OpenAI Chat Completions-style requests, you can use Gambit as a drop-in-ish wrapper that applies a deck prompt and can execute only deck-defined tools (action decks).
import {
chatCompletionsWithDeck,
createOpenRouterProvider,
} from "jsr:@bolt-foundry/gambit";
const provider = createOpenRouterProvider({
apiKey: Deno.env.get("OPENROUTER_API_KEY")!,
});
const resp = await chatCompletionsWithDeck({
deckPath: "./path/to/root.deck.md",
modelProvider: provider,
request: {
model: "openai/gpt-4o-mini",
messages: [{ role: "user", content: "hello" }],
},
});
console.log(resp.choices[0].message);- Decks may declare
handlerswithonError,onBusy, andonIdle.onIntervalis still accepted but deprecated (alias foronBusy). - Busy handler input:
{kind:"busy", source:{deckPath, actionName}, trigger:{reason:"timeout", elapsedMs}, childInput}plusdelayMs/repeatMsknobs. - Idle handler input:
{kind:"idle", source:{deckPath}, trigger:{reason:"idle_timeout", elapsedMs}}withdelayMs(and optionalrepeatMsif provided). - Error handler input:
{kind:"error", source, error:{message}, childInput}and should return an envelope{message?, code?, status?, meta?, payload?}. - Example implementations live under
examples/handlers_tsandexamples/handlers_md. - Debug UI streams handler output in a “status” lane (busy/idle) separate from assistant turns.
- Swap
modelParams.modelor pass--model/--model-forceto test other providers. - Add
actionDecksto a deck and call child decks; usespawnAndWaitinside compute decks. - Use
--streamand--verbosewhile iterating; pass--trace <file>to capture JSONL traces.