The response has been limited to 50k tokens of the smallest files in the repo. You can remove this limitation by removing the max tokens filter.
├── .gitignore
├── CLAUDE.md
├── LICENSE
├── Makefile
├── README.md
├── content
    ├── appendix-13-pre-fetch.md
    ├── brief-history-of-software.md
    ├── factor-01-natural-language-to-tool-calls.md
    ├── factor-02-own-your-prompts.md
    ├── factor-03-own-your-context-window.md
    ├── factor-04-tools-are-structured-outputs.md
    ├── factor-05-unify-execution-state.md
    ├── factor-06-launch-pause-resume.md
    ├── factor-07-contact-humans-with-tools.md
    ├── factor-08-own-your-control-flow.md
    ├── factor-09-compact-errors.md
    ├── factor-1-natural-language-to-tool-calls.md
    ├── factor-10-small-focused-agents.md
    ├── factor-11-trigger-from-anywhere.md
    ├── factor-12-stateless-reducer.md
    ├── factor-2-own-your-prompts.md
    ├── factor-3-own-your-context-window.md
    ├── factor-4-tools-are-structured-outputs.md
    ├── factor-5-unify-execution-state.md
    ├── factor-6-launch-pause-resume.md
    ├── factor-7-contact-humans-with-tools.md
    ├── factor-8-own-your-control-flow.md
    └── factor-9-compact-errors.md
├── drafts
    ├── a2h-spec.md
    └── ah2-openapi.json
├── hack
    └── contributors_markdown
    │   ├── .python-version
    │   ├── README.md
    │   ├── contributors_markdown.py
    │   ├── pyproject.toml
    │   └── uv.lock
├── img
    ├── 010-software-dag.png
    ├── 015-dag-orchestrators.png
    ├── 020-dags-with-ml.png
    ├── 025-agent-dag.png
    ├── 026-agent-dag-lines.png
    ├── 027-agent-loop-animation.gif
    ├── 027-agent-loop-animation.mp4
    ├── 027-agent-loop-dag.png
    ├── 027-agent-loop.png
    ├── 028-micro-agent-dag.png
    ├── 029-deploybot-high-level.png
    ├── 030-deploybot-animation.gif
    ├── 030-deploybot-animation.mp4
    ├── 031-deploybot-animation-5.gif
    ├── 031-deploybot-animation-5.mp4
    ├── 031-deploybot-animation.gif
    ├── 031-deploybot-animation.mp4
    ├── 033-deploybot.gif
    ├── 035-deploybot-conversation.png
    ├── 040-4-components.png
    ├── 110-natural-language-tool-calls.png
    ├── 120-own-your-prompts.png
    ├── 130-own-your-context-building.png
    ├── 140-tools-are-just-structured-outputs.png
    ├── 150-all-state-in-context-window.png
    ├── 150-unify-state.png
    ├── 155-unify-state-animation.gif
    ├── 160-pause-resume-with-simple-apis.png
    ├── 165-pause-resume-animation.gif
    ├── 170-contact-humans-with-tools.png
    ├── 175-outer-loop-agents.png
    ├── 180-control-flow.png
    ├── 190-factor-9-errors-static.png
    ├── 195-factor-9-errors.gif
    ├── 1a0-small-focused-agents.png
    ├── 1a5-agent-scope-grow.gif
    ├── 1b0-trigger-from-anywhere.png
    ├── 1c0-stateless-reducer.png
    ├── 1c5-agent-foldl.png
    └── 220-context-engineering.png
├── packages
    ├── create-12-factor-agent
    │   └── template
    │   │   ├── .gitignore
    │   │   ├── README.md
    │   │   ├── baml_src
    │   │       ├── agent.baml
    │   │       ├── clients.baml
    │   │       ├── generators.baml
    │   │       └── tool_calculator.baml
    │   │   ├── package-lock.json
    │   │   ├── package.json
    │   │   ├── src
    │   │       ├── a2h.ts
    │   │       ├── agent.ts
    │   │       ├── cli.ts
    │   │       ├── index.ts
    │   │       ├── server.ts
    │   │       └── state.ts
    │   │   └── tsconfig.json
    └── walkthroughgen
    │   ├── .gitignore
    │   ├── examples
    │       ├── typescript
    │       │   ├── .gitignore
    │       │   ├── walkthrough.yaml
    │       │   └── walkthrough
    │       │   │   ├── 00-package-lock.json
    │       │   │   ├── 00-package.json
    │       │   │   ├── 00-tsconfig.json
    │       │   │   ├── 01-index.ts
    │       │   │   ├── 02-cli.ts
    │       │   │   └── 02-index.ts
    │       └── walkthroughgen
    │       │   └── walkthrough.yaml
    │   ├── jest.config.js
    │   ├── package-lock.json
    │   ├── package.json
    │   ├── prompt.md
    │   ├── readme.md
    │   ├── src
    │       ├── cli.ts
    │       └── index.ts
    │   ├── test
    │       ├── e2e
    │       │   └── test-e2e.ts
    │       └── utils
    │       │   ├── console-mock.ts
    │       │   └── temp-dir.ts
    │   └── tsconfig.json
└── workshops
    ├── .gitignore
    ├── .python-version
    ├── 2025-05-17
        ├── .gitignore
        ├── package-lock.json
        ├── package.json
        ├── sections
        │   ├── 00-hello-world
        │   │   ├── README.md
        │   │   └── walkthrough
        │   │   │   ├── 00-.gitignore
        │   │   │   ├── 00-index.ts
        │   │   │   ├── 00-package.json
        │   │   │   └── 00-tsconfig.json
        │   ├── 01-cli-and-agent
        │   │   ├── .gitignore
        │   │   ├── README.md
        │   │   ├── src
        │   │   │   └── index.ts
        │   │   └── walkthrough
        │   │   │   ├── 01-agent.baml
        │   │   │   ├── 01-agent.ts
        │   │   │   ├── 01-cli.ts
        │   │   │   └── 01-index.ts
        │   ├── 02-calculator-tools
        │   │   ├── .gitignore
        │   │   ├── README.md
        │   │   ├── baml_src
        │   │   │   ├── agent.baml
        │   │   │   ├── clients.baml
        │   │   │   └── generators.baml
        │   │   ├── src
        │   │   │   ├── agent.ts
        │   │   │   ├── cli.ts
        │   │   │   └── index.ts
        │   │   └── walkthrough
        │   │   │   ├── 02-agent.baml
        │   │   │   └── 02-tool_calculator.baml
        │   └── 03-tool-loop
        │   │   ├── .gitignore
        │   │   ├── README.md
        │   │   ├── baml_src
        │   │       ├── agent.baml
        │   │       ├── clients.baml
        │   │       ├── generators.baml
        │   │       └── tool_calculator.baml
        │   │   ├── src
        │   │       ├── agent.ts
        │   │       ├── cli.ts
        │   │       └── index.ts
        │   │   └── walkthrough
        │   │       ├── 03-agent.ts
        │   │       └── 03b-agent.ts
        ├── tsconfig.json
        ├── walkthrough.md
        ├── walkthrough.yaml
        └── walkthrough
        │   ├── 00-.gitignore
        │   ├── 00-index.ts
        │   ├── 00-package.json
        │   ├── 00-tsconfig.json
        │   ├── 01-agent.baml
        │   ├── 01-agent.ts
        │   ├── 01-cli.ts
        │   ├── 01-index.ts
        │   ├── 02-agent.baml
        │   ├── 02-tool_calculator.baml
        │   ├── 03-agent.ts
        │   ├── 03b-agent.ts
        │   ├── 04-agent.baml
        │   ├── 04b-agent.baml
        │   ├── 04c-agent.baml
        │   ├── 05-agent.baml
        │   ├── 05-agent.ts
        │   ├── 05-cli.ts
        │   ├── 05b-agent.baml
        │   ├── 05c-agent.baml
        │   ├── 06-agent.baml
        │   ├── 07-agent.ts
        │   ├── 07b-agent.ts
        │   ├── 07c-agent.baml
        │   ├── 08-server.ts
        │   ├── 09-server.ts
        │   ├── 09-state.ts
        │   ├── 10-agent.ts
        │   ├── 10-server.ts
        │   ├── 11-cli.ts
        │   ├── 11-email-approve.png
        │   ├── 11-email-custom.png
        │   ├── 11-email-reject.png
        │   ├── 11b-cli.ts
        │   ├── 11c-cli.ts
        │   ├── 12-1-server-init.ts
        │   ├── 12-server.ts
        │   ├── 12a-server.ts
        │   ├── 12aa-server.ts
        │   └── 12b-server.ts
    ├── 2025-05
        ├── .gitignore
        ├── Makefile
        ├── final
        │   ├── .gitignore
        │   ├── baml_src
        │   │   ├── agent.baml
        │   │   ├── clients.baml
        │   │   ├── generators.baml
        │   │   └── tool_calculator.baml
        │   ├── package-lock.json
        │   ├── package.json
        │   ├── src
        │   │   ├── agent.ts
        │   │   ├── cli.ts
        │   │   ├── index.ts
        │   │   ├── server.ts
        │   │   └── state.ts
        │   └── tsconfig.json
        ├── sections
        │   ├── 00-hello-world
        │   │   ├── README.md
        │   │   └── walkthrough
        │   │   │   ├── 00-.gitignore
        │   │   │   ├── 00-index.ts
        │   │   │   ├── 00-package.json
        │   │   │   └── 00-tsconfig.json
        │   ├── 01-cli-and-agent
        │   │   ├── .gitignore
        │   │   ├── README.md
        │   │   ├── package-lock.json
        │   │   ├── package.json
        │   │   ├── src
        │   │   │   └── index.ts
        │   │   ├── tsconfig.json
        │   │   └── walkthrough
        │   │   │   ├── 01-agent.baml
        │   │   │   ├── 01-agent.ts
        │   │   │   ├── 01-cli.ts
        │   │   │   └── 01-index.ts
        │   ├── 02-calculator-tools
        │   │   ├── .gitignore
        │   │   ├── README.md
        │   │   ├── baml_src
        │   │   │   ├── agent.baml
        │   │   │   ├── clients.baml
        │   │   │   └── generators.baml
        │   │   ├── package-lock.json
        │   │   ├── package.json
        │   │   ├── src
        │   │   │   ├── agent.ts
        │   │   │   ├── cli.ts
        │   │   │   └── index.ts
        │   │   ├── tsconfig.json
        │   │   └── walkthrough
        │   │   │   ├── 02-agent.baml
        │   │   │   └── 02-tool_calculator.baml
        │   ├── 03-tool-loop
        │   │   ├── .gitignore
        │   │   ├── README.md
        │   │   ├── baml_src
        │   │   │   ├── agent.baml
        │   │   │   ├── clients.baml
        │   │   │   ├── generators.baml
        │   │   │   └── tool_calculator.baml
        │   │   ├── package-lock.json
        │   │   ├── package.json
        │   │   ├── src
        │   │   │   ├── agent.ts
        │   │   │   ├── cli.ts
        │   │   │   └── index.ts
        │   │   ├── tsconfig.json
        │   │   └── walkthrough
        │   │   │   ├── 03-agent.ts
        │   │   │   └── 03b-agent.ts
        │   ├── 04-baml-tests
        │   │   ├── .gitignore
        │   │   ├── README.md
        │   │   ├── baml_src
        │   │   │   ├── agent.baml
        │   │   │   ├── clients.baml
        │   │   │   ├── generators.baml
        │   │   │   └── tool_calculator.baml
        │   │   ├── package-lock.json
        │   │   ├── package.json
        │   │   ├── src
        │   │   │   ├── agent.ts
        │   │   │   ├── cli.ts
        │   │   │   └── index.ts
        │   │   ├── tsconfig.json
        │   │   └── walkthrough
        │   │   │   ├── 04-agent.baml
        │   │   │   ├── 04b-agent.baml
        │   │   │   └── 04c-agent.baml
        │   ├── 05-human-tools
        │   │   ├── .gitignore
        │   │   ├── README.md
        │   │   ├── baml_src
        │   │   │   ├── agent.baml
        │   │   │   ├── clients.baml
        │   │   │   ├── generators.baml
        │   │   │   └── tool_calculator.baml
        │   │   ├── package-lock.json
        │   │   ├── package.json
        │   │   ├── src
        │   │   │   ├── agent.ts
        │   │   │   ├── cli.ts
        │   │   │   └── index.ts
        │   │   ├── tsconfig.json
        │   │   └── walkthrough
        │   │   │   ├── 05-agent.baml
        │   │   │   ├── 05-agent.ts
        │   │   │   ├── 05-cli.ts
        │   │   │   ├── 05b-agent.baml
        │   │   │   └── 05c-agent.baml
        │   ├── 06-customize-prompt
        │   │   ├── .gitignore
        │   │   ├── README.md
        │   │   ├── baml_src
        │   │   │   ├── agent.baml
        │   │   │   ├── clients.baml
        │   │   │   ├── generators.baml
        │   │   │   └── tool_calculator.baml
        │   │   ├── package-lock.json
        │   │   ├── package.json
        │   │   ├── src
        │   │   │   ├── agent.ts
        │   │   │   ├── cli.ts
        │   │   │   └── index.ts
        │   │   ├── tsconfig.json
        │   │   └── walkthrough
        │   │   │   └── 06-agent.baml
        │   ├── 07-context-window
        │   │   ├── .gitignore
        │   │   ├── README.md
        │   │   ├── baml_src
        │   │   │   ├── agent.baml
        │   │   │   ├── clients.baml
        │   │   │   ├── generators.baml
        │   │   │   └── tool_calculator.baml
        │   │   ├── package-lock.json
        │   │   ├── package.json
        │   │   ├── src
        │   │   │   ├── agent.ts
        │   │   │   ├── cli.ts
        │   │   │   └── index.ts
        │   │   ├── tsconfig.json
        │   │   └── walkthrough
        │   │   │   ├── 07-agent.ts
        │   │   │   ├── 07b-agent.ts
        │   │   │   └── 07c-agent.baml
        │   ├── 08-api-endpoints
        │   │   ├── .gitignore
        │   │   ├── README.md
        │   │   ├── baml_src
        │   │   │   ├── agent.baml
        │   │   │   ├── clients.baml
        │   │   │   ├── generators.baml
        │   │   │   └── tool_calculator.baml
        │   │   ├── package-lock.json
        │   │   ├── package.json
        │   │   ├── src
        │   │   │   ├── agent.ts
        │   │   │   ├── cli.ts
        │   │   │   └── index.ts
        │   │   ├── tsconfig.json
        │   │   └── walkthrough
        │   │   │   └── 08-server.ts
        │   ├── 09-state-management
        │   │   ├── .gitignore
        │   │   ├── README.md
        │   │   ├── baml_src
        │   │   │   ├── agent.baml
        │   │   │   ├── clients.baml
        │   │   │   ├── generators.baml
        │   │   │   └── tool_calculator.baml
        │   │   ├── package-lock.json
        │   │   ├── package.json
        │   │   ├── src
        │   │   │   ├── agent.ts
        │   │   │   ├── cli.ts
        │   │   │   ├── index.ts
        │   │   │   └── server.ts
        │   │   ├── tsconfig.json
        │   │   └── walkthrough
        │   │   │   ├── 09-server.ts
        │   │   │   └── 09-state.ts
        │   ├── 10-human-approval
        │   │   ├── .gitignore
        │   │   ├── README.md
        │   │   ├── baml_src
        │   │   │   ├── agent.baml
        │   │   │   ├── clients.baml
        │   │   │   ├── generators.baml
        │   │   │   └── tool_calculator.baml
        │   │   ├── package-lock.json
        │   │   ├── package.json
        │   │   ├── src
        │   │   │   ├── agent.ts
        │   │   │   ├── cli.ts
        │   │   │   ├── index.ts
        │   │   │   ├── server.ts
        │   │   │   └── state.ts
        │   │   ├── tsconfig.json
        │   │   └── walkthrough
        │   │   │   ├── 10-agent.ts
        │   │   │   └── 10-server.ts
        │   ├── 11-humanlayer-approval
        │   │   ├── .gitignore
        │   │   ├── README.md
        │   │   ├── baml_src
        │   │   │   ├── agent.baml
        │   │   │   ├── clients.baml
        │   │   │   ├── generators.baml
        │   │   │   └── tool_calculator.baml
        │   │   ├── package-lock.json
        │   │   ├── package.json
        │   │   ├── src
        │   │   │   ├── agent.ts
        │   │   │   ├── cli.ts
        │   │   │   ├── index.ts
        │   │   │   ├── server.ts
        │   │   │   └── state.ts
        │   │   ├── tsconfig.json
        │   │   └── walkthrough
        │   │   │   ├── 11-cli.ts
        │   │   │   ├── 11b-cli.ts
        │   │   │   └── 11c-cli.ts
        │   ├── 12-humanlayer-webhook
        │   │   ├── .gitignore
        │   │   ├── README.md
        │   │   ├── baml_src
        │   │   │   ├── agent.baml
        │   │   │   ├── clients.baml
        │   │   │   ├── generators.baml
        │   │   │   └── tool_calculator.baml
        │   │   ├── package-lock.json
        │   │   ├── package.json
        │   │   ├── src
        │   │   │   ├── agent.ts
        │   │   │   ├── cli.ts
        │   │   │   ├── index.ts
        │   │   │   ├── server.ts
        │   │   │   └── state.ts
        │   │   ├── tsconfig.json
        │   │   └── walkthrough
        │   │   │   ├── 12-1-server-init.ts
        │   │   │   └── 12a-server.ts
        │   └── final
        │   │   ├── .gitignore
        │   │   ├── README.md
        │   │   ├── baml_src
        │   │       ├── agent.baml
        │   │       ├── clients.baml
        │   │       ├── generators.baml
        │   │       └── tool_calculator.baml
        │   │   ├── package-lock.json
        │   │   ├── package.json
        │   │   ├── src
        │   │       ├── agent.ts
        │   │       ├── cli.ts
        │   │       ├── index.ts
        │   │       ├── server.ts
        │   │       └── state.ts
        │   │   └── tsconfig.json
        ├── walkthrough.md
        ├── walkthrough.yaml
        └── walkthrough
        │   ├── 00-.gitignore
        │   ├── 00-index.ts
        │   ├── 00-package.json
        │   ├── 00-tsconfig.json
        │   ├── 01-agent.baml
        │   ├── 01-agent.ts
        │   ├── 01-cli.ts
        │   ├── 01-index.ts
        │   ├── 02-agent.baml
        │   ├── 02-tool_calculator.baml
        │   ├── 03-agent.ts
        │   ├── 03b-agent.ts
        │   ├── 04-agent.baml
        │   ├── 04b-agent.baml
        │   ├── 04c-agent.baml
        │   ├── 05-agent.baml
        │   ├── 05-agent.ts
        │   ├── 05-cli.ts
        │   ├── 05b-agent.baml
        │   ├── 05c-agent.baml
        │   ├── 06-agent.baml
        │   ├── 07-agent.ts
        │   ├── 07b-agent.ts
        │   ├── 07c-agent.baml
        │   ├── 08-server.ts
        │   ├── 09-server.ts
        │   ├── 09-state.ts
        │   ├── 10-agent.ts
        │   ├── 10-server.ts
        │   ├── 11-cli.ts
        │   ├── 11-email-approve.png
        │   ├── 11-email-custom.png
        │   ├── 11-email-reject.png
        │   ├── 11b-cli.ts
        │   ├── 11c-cli.ts
        │   ├── 12-1-server-init.ts
        │   ├── 12-server.ts
        │   ├── 12a-server.ts
        │   ├── 12aa-server.ts
        │   └── 12b-server.ts
    └── 2025-07-16
        ├── .gitignore
        ├── CLAUDE.md
        ├── hack
            ├── analyze_log_capture.py
            ├── inspect_notebook.py
            ├── minimal_test.ipynb
            ├── test_log_capture.sh
            └── testing.md
        ├── pyproject.toml
        ├── test_notebook_colab_sim.sh
        ├── uv.lock
        ├── walkthrough.yaml
        ├── walkthrough
            ├── 00-.gitignore
            ├── 00-main.py
            ├── 00-package.json
            ├── 00-tsconfig.json
            ├── 01-agent.baml
            ├── 01-agent.py
            ├── 01-main.py
            ├── 02-agent.baml
            ├── 02-main.py
            ├── 02-tool_calculator.baml
            ├── 03-agent.py
            ├── 03-main.py
            ├── 03b-agent.py
            ├── 03b-agent.ts
            ├── 04-agent.baml
            ├── 04b-agent.baml
            ├── 04c-agent.baml
            ├── 05-agent.baml
            ├── 05-agent.py
            ├── 05-main.py
            ├── 05b-agent.baml
            ├── 05c-agent.baml
            ├── 06-agent.baml
            ├── 07-agent.py
            ├── 07-main.py
            ├── 07b-agent.ts
            ├── 07c-agent.baml
            ├── 08-server.ts
            ├── 09-server.ts
            ├── 09-state.ts
            ├── 10-agent.ts
            ├── 10-server.ts
            ├── 11-cli.ts
            ├── 11-email-approve.png
            ├── 11-email-custom.png
            ├── 11-email-reject.png
            ├── 11b-cli.ts
            ├── 11c-cli.ts
            ├── 12-1-server-init.ts
            ├── 12-server.ts
            ├── 12a-server.ts
            ├── 12aa-server.ts
            └── 12b-server.ts
        ├── walkthrough_python_enhanced.yaml
        ├── walkthroughgen_py.py
        └── workshop_final.ipynb


/.gitignore:
--------------------------------------------------------------------------------
1 | .promptx
2 | 


--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
 1 | # Makefile for launch compatibility
 2 | .PHONY: setup teardown
 3 | 
 4 | setup:
 5 | 	@echo "Setting up project..."
 6 | 	@npm install || bun install || yarn install
 7 | 	@echo "Setup complete!"
 8 | 
 9 | teardown:
10 | 	@echo "Tearing down project..."
11 | 	@rm -rf node_modules
12 | 	@echo "Teardown complete!"
13 | 


--------------------------------------------------------------------------------
/content/factor-1-natural-language-to-tool-calls.md:
--------------------------------------------------------------------------------
1 | [Moved to factor-01-natural-language-to-tool-calls.md](./factor-01-natural-language-to-tool-calls.md)
2 | 


--------------------------------------------------------------------------------
/content/factor-12-stateless-reducer.md:
--------------------------------------------------------------------------------
 1 | [← Back to README](https://github.com/humanlayer/12-factor-agents/blob/main/README.md)
 2 | 
 3 | ### 12. Make your agent a stateless reducer
 4 | 
 5 | Okay so we're over 1000 lines of markdown at this point. This one is mostly just for fun.
 6 | 
 7 | ![1c0-stateless-reducer](https://github.com/humanlayer/12-factor-agents/blob/main/img/1c0-stateless-reducer.png)
 8 | 
 9 | 
10 | ![1c5-agent-foldl](https://github.com/humanlayer/12-factor-agents/blob/main/img/1c5-agent-foldl.png)
11 | 
12 | [← Trigger From Anywhere](https://github.com/humanlayer/12-factor-agents/blob/main/content/factor-11-trigger-from-anywhere.md) | [Appendix - Pre-Fetch Context →](https://github.com/humanlayer/12-factor-agents/blob/main/content/appendix-13-pre-fetch.md)
13 | 


--------------------------------------------------------------------------------
/content/factor-2-own-your-prompts.md:
--------------------------------------------------------------------------------
1 | [Moved to factor-02-own-your-prompts.md](./factor-02-own-your-prompts.md)
2 | 


--------------------------------------------------------------------------------
/content/factor-3-own-your-context-window.md:
--------------------------------------------------------------------------------
1 | [Moved to factor-03-own-your-context-window.md](./factor-03-own-your-context-window.md)
2 | 


--------------------------------------------------------------------------------
/content/factor-4-tools-are-structured-outputs.md:
--------------------------------------------------------------------------------
1 | [Moved to factor-04-tools-are-structured-outputs.md](./factor-04-tools-are-structured-outputs.md)
2 | 


--------------------------------------------------------------------------------
/content/factor-5-unify-execution-state.md:
--------------------------------------------------------------------------------
1 | [Moved to factor-05-unify-execution-state.md](./factor-05-unify-execution-state.md)
2 | 


--------------------------------------------------------------------------------
/content/factor-6-launch-pause-resume.md:
--------------------------------------------------------------------------------
1 | [Moved to factor-06-launch-pause-resume.md](./factor-06-launch-pause-resume.md)
2 | 


--------------------------------------------------------------------------------
/content/factor-7-contact-humans-with-tools.md:
--------------------------------------------------------------------------------
1 | [Moved to factor-07-contact-humans-with-tools.md](./factor-07-contact-humans-with-tools.md)
2 | 


--------------------------------------------------------------------------------
/content/factor-8-own-your-control-flow.md:
--------------------------------------------------------------------------------
1 | [Moved to factor-08-own-your-control-flow.md](./factor-08-own-your-control-flow.md)
2 | 


--------------------------------------------------------------------------------
/content/factor-9-compact-errors.md:
--------------------------------------------------------------------------------
1 | [Moved to factor-09-compact-errors.md](./factor-09-compact-errors.md)
2 | 


--------------------------------------------------------------------------------
/drafts/ah2-openapi.json:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humanlayer/12-factor-agents/d20c728368bf9c189d6d7aab704744decb6ec0cc/drafts/ah2-openapi.json


--------------------------------------------------------------------------------
/hack/contributors_markdown/.python-version:
--------------------------------------------------------------------------------
1 | 3.13
2 | 


--------------------------------------------------------------------------------
/hack/contributors_markdown/README.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humanlayer/12-factor-agents/d20c728368bf9c189d6d7aab704744decb6ec0cc/hack/contributors_markdown/README.md


--------------------------------------------------------------------------------
/hack/contributors_markdown/pyproject.toml:
--------------------------------------------------------------------------------
 1 | [project]
 2 | name = "contributors-markdown"
 3 | version = "0.1.0"
 4 | description = "Add your description here"
 5 | readme = "README.md"
 6 | requires-python = ">=3.13"
 7 | dependencies = [
 8 |     "requests>=2.32.3",
 9 | ]
10 | 


--------------------------------------------------------------------------------
/img/010-software-dag.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humanlayer/12-factor-agents/d20c728368bf9c189d6d7aab704744decb6ec0cc/img/010-software-dag.png


--------------------------------------------------------------------------------
/img/015-dag-orchestrators.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humanlayer/12-factor-agents/d20c728368bf9c189d6d7aab704744decb6ec0cc/img/015-dag-orchestrators.png


--------------------------------------------------------------------------------
/img/020-dags-with-ml.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humanlayer/12-factor-agents/d20c728368bf9c189d6d7aab704744decb6ec0cc/img/020-dags-with-ml.png


--------------------------------------------------------------------------------
/img/025-agent-dag.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humanlayer/12-factor-agents/d20c728368bf9c189d6d7aab704744decb6ec0cc/img/025-agent-dag.png


--------------------------------------------------------------------------------
/img/026-agent-dag-lines.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humanlayer/12-factor-agents/d20c728368bf9c189d6d7aab704744decb6ec0cc/img/026-agent-dag-lines.png


--------------------------------------------------------------------------------
/img/027-agent-loop-animation.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humanlayer/12-factor-agents/d20c728368bf9c189d6d7aab704744decb6ec0cc/img/027-agent-loop-animation.gif


--------------------------------------------------------------------------------
/img/027-agent-loop-animation.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humanlayer/12-factor-agents/d20c728368bf9c189d6d7aab704744decb6ec0cc/img/027-agent-loop-animation.mp4


--------------------------------------------------------------------------------
/img/027-agent-loop-dag.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humanlayer/12-factor-agents/d20c728368bf9c189d6d7aab704744decb6ec0cc/img/027-agent-loop-dag.png


--------------------------------------------------------------------------------
/img/027-agent-loop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humanlayer/12-factor-agents/d20c728368bf9c189d6d7aab704744decb6ec0cc/img/027-agent-loop.png


--------------------------------------------------------------------------------
/img/028-micro-agent-dag.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humanlayer/12-factor-agents/d20c728368bf9c189d6d7aab704744decb6ec0cc/img/028-micro-agent-dag.png


--------------------------------------------------------------------------------
/img/029-deploybot-high-level.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humanlayer/12-factor-agents/d20c728368bf9c189d6d7aab704744decb6ec0cc/img/029-deploybot-high-level.png


--------------------------------------------------------------------------------
/img/030-deploybot-animation.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humanlayer/12-factor-agents/d20c728368bf9c189d6d7aab704744decb6ec0cc/img/030-deploybot-animation.gif


--------------------------------------------------------------------------------
/img/030-deploybot-animation.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humanlayer/12-factor-agents/d20c728368bf9c189d6d7aab704744decb6ec0cc/img/030-deploybot-animation.mp4


--------------------------------------------------------------------------------
/img/031-deploybot-animation-5.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humanlayer/12-factor-agents/d20c728368bf9c189d6d7aab704744decb6ec0cc/img/031-deploybot-animation-5.gif


--------------------------------------------------------------------------------
/img/031-deploybot-animation-5.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humanlayer/12-factor-agents/d20c728368bf9c189d6d7aab704744decb6ec0cc/img/031-deploybot-animation-5.mp4


--------------------------------------------------------------------------------
/img/031-deploybot-animation.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humanlayer/12-factor-agents/d20c728368bf9c189d6d7aab704744decb6ec0cc/img/031-deploybot-animation.gif


--------------------------------------------------------------------------------
/img/031-deploybot-animation.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humanlayer/12-factor-agents/d20c728368bf9c189d6d7aab704744decb6ec0cc/img/031-deploybot-animation.mp4


--------------------------------------------------------------------------------
/img/033-deploybot.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humanlayer/12-factor-agents/d20c728368bf9c189d6d7aab704744decb6ec0cc/img/033-deploybot.gif


--------------------------------------------------------------------------------
/img/035-deploybot-conversation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humanlayer/12-factor-agents/d20c728368bf9c189d6d7aab704744decb6ec0cc/img/035-deploybot-conversation.png


--------------------------------------------------------------------------------
/img/040-4-components.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humanlayer/12-factor-agents/d20c728368bf9c189d6d7aab704744decb6ec0cc/img/040-4-components.png


--------------------------------------------------------------------------------
/img/110-natural-language-tool-calls.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humanlayer/12-factor-agents/d20c728368bf9c189d6d7aab704744decb6ec0cc/img/110-natural-language-tool-calls.png


--------------------------------------------------------------------------------
/img/120-own-your-prompts.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humanlayer/12-factor-agents/d20c728368bf9c189d6d7aab704744decb6ec0cc/img/120-own-your-prompts.png


--------------------------------------------------------------------------------
/img/130-own-your-context-building.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humanlayer/12-factor-agents/d20c728368bf9c189d6d7aab704744decb6ec0cc/img/130-own-your-context-building.png


--------------------------------------------------------------------------------
/img/140-tools-are-just-structured-outputs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humanlayer/12-factor-agents/d20c728368bf9c189d6d7aab704744decb6ec0cc/img/140-tools-are-just-structured-outputs.png


--------------------------------------------------------------------------------
/img/150-all-state-in-context-window.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humanlayer/12-factor-agents/d20c728368bf9c189d6d7aab704744decb6ec0cc/img/150-all-state-in-context-window.png


--------------------------------------------------------------------------------
/img/150-unify-state.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humanlayer/12-factor-agents/d20c728368bf9c189d6d7aab704744decb6ec0cc/img/150-unify-state.png


--------------------------------------------------------------------------------
/img/155-unify-state-animation.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humanlayer/12-factor-agents/d20c728368bf9c189d6d7aab704744decb6ec0cc/img/155-unify-state-animation.gif


--------------------------------------------------------------------------------
/img/160-pause-resume-with-simple-apis.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humanlayer/12-factor-agents/d20c728368bf9c189d6d7aab704744decb6ec0cc/img/160-pause-resume-with-simple-apis.png


--------------------------------------------------------------------------------
/img/165-pause-resume-animation.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humanlayer/12-factor-agents/d20c728368bf9c189d6d7aab704744decb6ec0cc/img/165-pause-resume-animation.gif


--------------------------------------------------------------------------------
/img/170-contact-humans-with-tools.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humanlayer/12-factor-agents/d20c728368bf9c189d6d7aab704744decb6ec0cc/img/170-contact-humans-with-tools.png


--------------------------------------------------------------------------------
/img/175-outer-loop-agents.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humanlayer/12-factor-agents/d20c728368bf9c189d6d7aab704744decb6ec0cc/img/175-outer-loop-agents.png


--------------------------------------------------------------------------------
/img/180-control-flow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humanlayer/12-factor-agents/d20c728368bf9c189d6d7aab704744decb6ec0cc/img/180-control-flow.png


--------------------------------------------------------------------------------
/img/190-factor-9-errors-static.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humanlayer/12-factor-agents/d20c728368bf9c189d6d7aab704744decb6ec0cc/img/190-factor-9-errors-static.png


--------------------------------------------------------------------------------
/img/195-factor-9-errors.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humanlayer/12-factor-agents/d20c728368bf9c189d6d7aab704744decb6ec0cc/img/195-factor-9-errors.gif


--------------------------------------------------------------------------------
/img/1a0-small-focused-agents.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humanlayer/12-factor-agents/d20c728368bf9c189d6d7aab704744decb6ec0cc/img/1a0-small-focused-agents.png


--------------------------------------------------------------------------------
/img/1a5-agent-scope-grow.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humanlayer/12-factor-agents/d20c728368bf9c189d6d7aab704744decb6ec0cc/img/1a5-agent-scope-grow.gif


--------------------------------------------------------------------------------
/img/1b0-trigger-from-anywhere.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humanlayer/12-factor-agents/d20c728368bf9c189d6d7aab704744decb6ec0cc/img/1b0-trigger-from-anywhere.png


--------------------------------------------------------------------------------
/img/1c0-stateless-reducer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humanlayer/12-factor-agents/d20c728368bf9c189d6d7aab704744decb6ec0cc/img/1c0-stateless-reducer.png


--------------------------------------------------------------------------------
/img/1c5-agent-foldl.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humanlayer/12-factor-agents/d20c728368bf9c189d6d7aab704744decb6ec0cc/img/1c5-agent-foldl.png


--------------------------------------------------------------------------------
/img/220-context-engineering.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humanlayer/12-factor-agents/d20c728368bf9c189d6d7aab704744decb6ec0cc/img/220-context-engineering.png


--------------------------------------------------------------------------------
/packages/create-12-factor-agent/template/.gitignore:
--------------------------------------------------------------------------------
1 | baml_client/
2 | node_modules/
3 | .threads/
4 | 


--------------------------------------------------------------------------------
/packages/create-12-factor-agent/template/baml_src/clients.baml:
--------------------------------------------------------------------------------
 1 | // Learn more about clients at https://docs.boundaryml.com/docs/snippets/clients/overview
 2 | 
 3 | client<llm> CustomGPT4o {
 4 |   provider openai
 5 |   options {
 6 |     model "gpt-4o"
 7 |     api_key env.OPENAI_API_KEY
 8 |   }
 9 | }
10 | 
11 | client<llm> CustomGPT4oMini {
12 |   provider openai
13 |   retry_policy Exponential
14 |   options {
15 |     model "gpt-4o-mini"
16 |     api_key env.OPENAI_API_KEY
17 |   }
18 | }
19 | 
20 | client<llm> CustomSonnet {
21 |   provider anthropic
22 |   options {
23 |     model "claude-3-5-sonnet-20241022"
24 |     api_key env.ANTHROPIC_API_KEY
25 |   }
26 | }
27 | 
28 | 
29 | client<llm> CustomHaiku {
30 |   provider anthropic
31 |   retry_policy Constant
32 |   options {
33 |     model "claude-3-haiku-20240307"
34 |     api_key env.ANTHROPIC_API_KEY
35 |   }
36 | }
37 | 
38 | // https://docs.boundaryml.com/docs/snippets/clients/round-robin
39 | client<llm> CustomFast {
40 |   provider round-robin
41 |   options {
42 |     // This will alternate between the two clients
43 |     strategy [CustomGPT4oMini, CustomHaiku]
44 |   }
45 | }
46 | 
47 | // https://docs.boundaryml.com/docs/snippets/clients/fallback
48 | client<llm> OpenaiFallback {
49 |   provider fallback
50 |   options {
51 |     // This will try the clients in order until one succeeds
52 |     strategy [CustomGPT4oMini, CustomGPT4oMini]
53 |   }
54 | }
55 | 
56 | // https://docs.boundaryml.com/docs/snippets/clients/retry
57 | retry_policy Constant {
58 |   max_retries 3
59 |   // Strategy is optional
60 |   strategy {
61 |     type constant_delay
62 |     delay_ms 200
63 |   }
64 | }
65 | 
66 | retry_policy Exponential {
67 |   max_retries 2
68 |   // Strategy is optional
69 |   strategy {
70 |     type exponential_backoff
71 |     delay_ms 300
72 |     multiplier 1.5
73 |     max_delay_ms 10000
74 |   }
75 | }


--------------------------------------------------------------------------------
/packages/create-12-factor-agent/template/baml_src/generators.baml:
--------------------------------------------------------------------------------
 1 | // This helps use auto generate libraries you can use in the language of
 2 | // your choice. You can have multiple generators if you use multiple languages.
 3 | // Just ensure that the output_dir is different for each generator.
 4 | generator target {
 5 |     // Valid values: "python/pydantic", "typescript", "ruby/sorbet", "rest/openapi"
 6 |     output_type "typescript"
 7 | 
 8 |     // Where the generated code will be saved (relative to baml_src/)
 9 |     output_dir "../"
10 | 
11 |     // The version of the BAML package you have installed (e.g. same version as your baml-py or @boundaryml/baml).
12 |     // The BAML VSCode extension version should also match this version.
13 |     version "0.88.0"
14 | 
15 |     // Valid values: "sync", "async"
16 |     // This controls what `b.FunctionName()` will be (sync or async).
17 |     default_client_mode async
18 | }
19 | 


--------------------------------------------------------------------------------
/packages/create-12-factor-agent/template/baml_src/tool_calculator.baml:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 | class AddTool {
 4 |     intent "add"
 5 |     a int | float
 6 |     b int | float
 7 | }
 8 | 
 9 | class SubtractTool {
10 |     intent "subtract"
11 |     a int | float
12 |     b int | float
13 | }
14 | 
15 | class MultiplyTool {
16 |     intent "multiply"
17 |     a int | float
18 |     b int | float
19 | }
20 | 
21 | class DivideTool {
22 |     intent "divide"
23 |     a int | float
24 |     b int | float
25 | }
26 | 
27 | 


--------------------------------------------------------------------------------
/packages/create-12-factor-agent/template/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "name": "my-agent",
 3 |     "version": "0.1.0",
 4 |     "private": true,
 5 |     "scripts": {
 6 |         "dev": "tsx src/index.ts",
 7 |         "build": "tsc"
 8 |     },
 9 |     "dependencies": {
10 |         "@boundaryml/baml": "latest",
11 |         "express": "^5.1.0",
12 |         "humanlayer": "^0.7.7",
13 |         "tsx": "^4.15.0",
14 |         "typescript": "^5.0.0",
15 |         "zod": "^3.25.64"
16 |     },
17 |     "devDependencies": {
18 |         "@types/express": "^5.0.1",
19 |         "@types/node": "^20.0.0",
20 |         "@typescript-eslint/eslint-plugin": "^6.0.0",
21 |         "@typescript-eslint/parser": "^6.0.0",
22 |         "eslint": "^8.0.0",
23 |         "supertest": "^7.1.0"
24 |     }
25 | }
26 | 


--------------------------------------------------------------------------------
/packages/create-12-factor-agent/template/src/index.ts:
--------------------------------------------------------------------------------
1 | import { cli } from "./cli"
2 | 
3 | async function main() {
4 |     await cli()
5 | }
6 | 
7 | main().catch(console.error)


--------------------------------------------------------------------------------
/packages/create-12-factor-agent/template/tsconfig.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "compilerOptions": {
 3 |       "target": "ES2017",
 4 |       "lib": ["esnext"],
 5 |       "allowJs": true,
 6 |       "skipLibCheck": true,
 7 |       "strict": true,
 8 |       "noEmit": true,
 9 |       "esModuleInterop": true,
10 |       "module": "esnext",
11 |       "moduleResolution": "bundler",
12 |       "resolveJsonModule": true,
13 |       "isolatedModules": true,
14 |       "jsx": "preserve",
15 |       "incremental": true,
16 |       "plugins": [],
17 |       "paths": {
18 |         "@/*": ["./*"]
19 |       }
20 |     },
21 |     "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
22 |     "exclude": ["node_modules", "walkthrough"]
23 |   }
24 |   


--------------------------------------------------------------------------------
/packages/walkthroughgen/.gitignore:
--------------------------------------------------------------------------------
1 | .tmptest*


--------------------------------------------------------------------------------
/packages/walkthroughgen/examples/typescript/.gitignore:
--------------------------------------------------------------------------------
1 | build/


--------------------------------------------------------------------------------
/packages/walkthroughgen/examples/typescript/walkthrough.yaml:
--------------------------------------------------------------------------------
 1 | title: "setting up a typescript cli"
 2 | text: "this is a walkthrough for setting up a typescript cli"
 3 | targets:
 4 |   - markdown: "./build/walkthrough.md" # generates a walkthrough.md file
 5 |     onChange: # default behavior - on changes, show diffs and cp commands
 6 |       diff: true
 7 |       cp: true
 8 |     newFiles: # when new files are created, just show the copy command
 9 |       cat: false
10 |       cp: true
11 |   - final: "./build/final" # outputs the final project to the final folder
12 |   - folders: "./build/by-section" # creates a separate working folder for each section
13 | sections:
14 |   - name: setup
15 |     title: "Copy initial files"
16 |     steps:
17 |       - file: {src: ./walkthrough/00-package.json, dest: package.json}
18 |       - file: {src: ./walkthrough/00-package-lock.json, dest: package-lock.json}
19 |       - file: {src: ./walkthrough/00-tsconfig.json, dest: tsconfig.json}
20 |   - name: initialize
21 |     title: "Initialize the project"
22 |     steps:
23 |       - text: "initialize the project"
24 |         command: |
25 |           npm install
26 |       - text: "then add index.ts"
27 |         file: {src: ./walkthrough/01-index.ts, dest: src/index.ts}
28 |       - text: "run it with tsx"
29 |         command: |
30 |           npx tsx src/index.ts
31 |         results:
32 |           - text: "you should see a hello world message"
33 |             code: |
34 |               hello world
35 |   - name: add-cli
36 |     title: "Add a CLI"
37 |     steps:
38 |       - text: "add a cli"
39 |         file: {src: ./walkthrough/02-cli.ts, dest: src/cli.ts}
40 |       - text: "update index.ts to use the cli"
41 |         file: {src: ./walkthrough/02-index.ts, dest: src/index.ts}


--------------------------------------------------------------------------------
/packages/walkthroughgen/examples/typescript/walkthrough/00-package-lock.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "name": "walkthroughgen",
 3 |     "version": "1.0.0",
 4 |     "lockfileVersion": 3,
 5 |     "requires": true,
 6 |     "packages": {
 7 |       "": {
 8 |         "name": "walkthroughgen",
 9 |         "version": "1.0.0",
10 |         "license": "ISC",
11 |         "dependencies": {
12 |           "typescript": "^5.8.3"
13 |         }
14 |       }
15 |     },
16 |     "node_modules/typescript": {
17 |       "version": "5.8.3",
18 |       "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
19 |       "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
20 |       "license": "Apache-2.0",
21 |       "bin": {
22 |         "tsc": "bin/tsc",
23 |         "tsserver": "bin/tsserver"
24 |       },
25 |       "engines": {
26 |         "node": ">=14.17"
27 |       }
28 |     }
29 |   }
30 |   


--------------------------------------------------------------------------------
/packages/walkthroughgen/examples/typescript/walkthrough/00-package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "name": "walkthroughgen",
 3 |     "version": "1.0.0",
 4 |     "main": "index.js",
 5 |     "scripts": {
 6 |       "test": "echo \"Error: no test specified\" && exit 1"
 7 |     },
 8 |     "keywords": [],
 9 |     "author": "",
10 |     "license": "ISC",
11 |     "description": "",
12 |     "dependencies": {
13 |       "typescript": "^5.8.3"
14 |     }
15 |   }
16 |   


--------------------------------------------------------------------------------
/packages/walkthroughgen/examples/typescript/walkthrough/00-tsconfig.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "compilerOptions": {
 3 |       "target": "es2016",
 4 |       "module": "commonjs",
 5 |       "esModuleInterop": true,
 6 |       "forceConsistentCasingInFileNames": true,
 7 |       "strict": true,
 8 |       "skipLibCheck": true
 9 |     },
10 |     "exclude": ["node_modules", "dist", "**/*walkthrough/**"]
11 |   }
12 |   


--------------------------------------------------------------------------------
/packages/walkthroughgen/examples/typescript/walkthrough/01-index.ts:
--------------------------------------------------------------------------------
1 | const main = () => {
2 |   console.log("hello world");
3 | };
4 | 
5 | main();


--------------------------------------------------------------------------------
/packages/walkthroughgen/examples/typescript/walkthrough/02-cli.ts:
--------------------------------------------------------------------------------
 1 | const cli = () => {
 2 |     const args = process.argv.slice(2);
 3 |     const command = args[0];
 4 |     const name = args[1];
 5 |     if (command === "create") {
 6 |         console.log(`Creating ${name}`);
 7 |     } else {
 8 |         console.log("Invalid command: ", command);
 9 |         console.log("available commands: create");
10 |     }
11 | };
12 | 
13 | cli();
14 | 
15 | 


--------------------------------------------------------------------------------
/packages/walkthroughgen/examples/typescript/walkthrough/02-index.ts:
--------------------------------------------------------------------------------
1 | const main = async () => {
2 |     return cli();
3 |   };
4 |   
5 | main().catch(console.error);
6 |   


--------------------------------------------------------------------------------
/packages/walkthroughgen/examples/walkthroughgen/walkthrough.yaml:
--------------------------------------------------------------------------------
 1 | title: "using walkthroughgen"
 2 | targets:
 3 |   - markdown: "./walkthrough.md" # generates a walkthrough.md file
 4 |     diffs: true
 5 |   - final: "./final" # outputs the final project to the final folder
 6 |   - folders: "./by-section" # creates a separate working folder for each section
 7 | init:
 8 |   - file: {src: ./walkthrough/00-package.json, dest: package.json}
 9 |   - file: {src: ./walkthrough/00-package-lock.json, dest: package-lock.json}
10 | sections:
11 |   - name: initialize
12 |     title: "initialize the project"
13 |     steps:
14 |       - text: "initialize walkthroughgen"
15 |         command: |
16 |           npx wtg init my-project
17 |           cd my-project
18 |       - text: "this will create an empty project with a walkthrough.yaml file"
19 |         command: |
20 |           ls -la
21 |           cat walkthrough.yaml
22 |         results:
23 |           - text: "you should see a walkthrough.yaml file"
24 |             code: |
25 |               # walkthrough.yaml
26 |               title: "hello world"
27 |               sections:
28 |                 - name: initialize
29 |                   title: "initialize the project"
30 |                   steps:
31 |                     - text: "initialize the project"
32 |                       command: |
33 |                         # your code here
34 |   - name: build
35 |     title: "build the project"
36 |     steps:
37 |       - text: "build the project"
38 |         command: |
39 |           npx wtg build
40 |       - text: "this will create a walkthrough.md file"
41 |         command: |
42 |           cat walkthrough.md
43 |         results:
44 | 


--------------------------------------------------------------------------------
/packages/walkthroughgen/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 |   preset: 'ts-jest',
3 |   testEnvironment: 'node',
4 |   testMatch: ['**/test/**/*.ts'],
5 |   testPathIgnorePatterns: ['/node_modules/', '/test/utils/'],
6 |   transform: {
7 |     '^.+\\.ts
#39;: 'ts-jest',
8 |   },
9 | }; 


--------------------------------------------------------------------------------
/packages/walkthroughgen/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "walkthroughgen",
 3 |   "version": "1.0.0",
 4 |   "main": "index.js",
 5 |   "scripts": {
 6 |     "test": "jest",
 7 |     "test:watch": "jest --watch"
 8 |   },
 9 |   "keywords": [],
10 |   "author": "",
11 |   "license": "ISC",
12 |   "description": "",
13 |   "dependencies": {
14 |     "@boundaryml/baml": "^0.85.0",
15 |     "@types/diff": "^7.0.2",
16 |     "@types/js-yaml": "^4.0.9",
17 |     "diff": "^7.0.0",
18 |     "js-yaml": "^4.1.0",
19 |     "typescript": "^5.8.3"
20 |   },
21 |   "devDependencies": {
22 |     "@types/jest": "^29.5.14",
23 |     "jest": "^29.7.0",
24 |     "ts-jest": "^29.3.2"
25 |   }
26 | }
27 | 


--------------------------------------------------------------------------------
/packages/walkthroughgen/src/index.ts:
--------------------------------------------------------------------------------
1 | import { cli } from "./cli";
2 | 
3 | const main = async () => {
4 |   cli(process.argv.slice(2));
5 | };
6 | 
7 | main().catch(console.error);
8 | 


--------------------------------------------------------------------------------
/packages/walkthroughgen/test/utils/console-mock.ts:
--------------------------------------------------------------------------------
 1 | /**
 2 |  * A utility function to mock console.log and console.error and capture their output
 3 |  * @param callback The function to execute while console is mocked
 4 |  * @returns The captured console output (both log and error messages)
 5 |  */
 6 | export const withMockedConsole = (callback: () => void): string => {
 7 |   const originalConsoleLog = console.log;
 8 |   const originalConsoleError = console.error;
 9 |   let capturedOutput: string[] = [];
10 |   
11 |   console.log = (...args: any[]) => {
12 |     capturedOutput.push(args.join(" "));
13 |   };
14 | 
15 |   console.error = (...args: any[]) => {
16 |     capturedOutput.push(args.join(" "));
17 |   };
18 | 
19 |   try {
20 |     callback();
21 |   } finally {
22 |     console.log = originalConsoleLog;
23 |     console.error = originalConsoleError;
24 |   }
25 | 
26 |   return capturedOutput.join("\n");
27 | };


--------------------------------------------------------------------------------
/packages/walkthroughgen/test/utils/temp-dir.ts:
--------------------------------------------------------------------------------
 1 | import { mkdtempSync, rmSync } from 'fs';
 2 | import { tmpdir } from 'os';
 3 | import { join } from 'path';
 4 | 
 5 | /**
 6 |  * Creates a temporary directory, executes a function with that directory, then removes it
 7 |  */
 8 | export function withTmpDir<T>(fn: (dir: string) => T): T {
 9 |   const dir = mkdtempSync(join(__dirname, '.tmptest'));
10 |   try {
11 |     return fn(dir);
12 |   } finally {
13 |     rmSync(dir, { recursive: true, force: true });
14 |   }
15 | }
16 | 


--------------------------------------------------------------------------------
/packages/walkthroughgen/tsconfig.json:
--------------------------------------------------------------------------------
 1 | 
 2 | {
 3 |     "compilerOptions": {
 4 |       "target": "es2016",
 5 |       "module": "commonjs",
 6 |       "esModuleInterop": true,
 7 |       "forceConsistentCasingInFileNames": true,
 8 |       "strict": true,
 9 |       "skipLibCheck": true
10 |     },
11 |     "exclude": ["node_modules", "dist", "**/walkthrough/**"]
12 |   }
13 |   


--------------------------------------------------------------------------------
/workshops/.gitignore:
--------------------------------------------------------------------------------
1 | baml_client/
2 | 


--------------------------------------------------------------------------------
/workshops/.python-version:
--------------------------------------------------------------------------------
1 | 3.11
2 | 


--------------------------------------------------------------------------------
/workshops/2025-05-17/.gitignore:
--------------------------------------------------------------------------------
1 | baml_src/*.baml
2 | src/*.ts
3 | package.json
4 | package-lock.json
5 | tsconfig.json
6 | build/
7 | 


--------------------------------------------------------------------------------
/workshops/2025-05-17/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "name": "my-agent",
 3 |     "version": "0.1.0",
 4 |     "private": true,
 5 |     "scripts": {
 6 |         "dev": "tsx src/index.ts",
 7 |         "build": "tsc"
 8 |     },
 9 |     "dependencies": {
10 |         "@boundaryml/baml": "^0.88.0",
11 |         "tsx": "^4.15.0",
12 |         "typescript": "^5.0.0"
13 |     },
14 |     "devDependencies": {
15 |         "@types/node": "^20.0.0",
16 |         "@typescript-eslint/eslint-plugin": "^6.0.0",
17 |         "@typescript-eslint/parser": "^6.0.0",
18 |         "eslint": "^8.0.0"
19 |     }
20 | }
21 | 


--------------------------------------------------------------------------------
/workshops/2025-05-17/sections/00-hello-world/walkthrough/00-.gitignore:
--------------------------------------------------------------------------------
1 | baml_client/
2 | node_modules/
3 | 


--------------------------------------------------------------------------------
/workshops/2025-05-17/sections/00-hello-world/walkthrough/00-index.ts:
--------------------------------------------------------------------------------
1 | async function hello(): Promise<void> {
2 |     console.log('hello, world!')
3 | }
4 | 
5 | async function main() {
6 |     await hello()
7 | }
8 | 
9 | main().catch(console.error)


--------------------------------------------------------------------------------
/workshops/2025-05-17/sections/00-hello-world/walkthrough/00-package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "name": "my-agent",
 3 |     "version": "0.1.0",
 4 |     "private": true,
 5 |     "scripts": {
 6 |       "dev": "tsx src/index.ts",
 7 |       "build": "tsc"
 8 |     },
 9 |     "dependencies": {
10 |       "tsx": "^4.15.0",
11 |       "typescript": "^5.0.0"
12 |     },
13 |     "devDependencies": {
14 |       "@types/node": "^20.0.0",
15 |       "@typescript-eslint/eslint-plugin": "^6.0.0",
16 |       "@typescript-eslint/parser": "^6.0.0",
17 |       "eslint": "^8.0.0"
18 |     }
19 |   }
20 |   


--------------------------------------------------------------------------------
/workshops/2025-05-17/sections/00-hello-world/walkthrough/00-tsconfig.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "compilerOptions": {
 3 |       "target": "ES2017",
 4 |       "lib": ["esnext"],
 5 |       "allowJs": true,
 6 |       "skipLibCheck": true,
 7 |       "strict": true,
 8 |       "noEmit": true,
 9 |       "esModuleInterop": true,
10 |       "module": "esnext",
11 |       "moduleResolution": "bundler",
12 |       "resolveJsonModule": true,
13 |       "isolatedModules": true,
14 |       "jsx": "preserve",
15 |       "incremental": true,
16 |       "plugins": [],
17 |       "paths": {
18 |         "@/*": ["./*"]
19 |       }
20 |     },
21 |     "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
22 |     "exclude": ["node_modules", "walkthrough"]
23 |   }
24 |   


--------------------------------------------------------------------------------
/workshops/2025-05-17/sections/01-cli-and-agent/.gitignore:
--------------------------------------------------------------------------------
1 | baml_client/
2 | node_modules/
3 | 


--------------------------------------------------------------------------------
/workshops/2025-05-17/sections/01-cli-and-agent/src/index.ts:
--------------------------------------------------------------------------------
1 | async function hello(): Promise<void> {
2 |     console.log('hello, world!')
3 | }
4 | 
5 | async function main() {
6 |     await hello()
7 | }
8 | 
9 | main().catch(console.error)


--------------------------------------------------------------------------------
/workshops/2025-05-17/sections/01-cli-and-agent/walkthrough/01-agent.baml:
--------------------------------------------------------------------------------
 1 | class DoneForNow {
 2 |   intent "done_for_now"
 3 |   message string 
 4 | }
 5 | 
 6 | client<llm> Qwen3 {
 7 |   provider "openai-generic"
 8 |   options {
 9 |     base_url env.BASETEN_BASE_URL
10 |     api_key env.BASETEN_API_KEY 
11 |   }
12 | }
13 | 
14 | function DetermineNextStep(
15 |     thread: string 
16 | ) -> DoneForNow {
17 |     client Qwen3
18 | 
19 |     // use /nothink for now because the thinking tokens (or streaming thereof) screw with baml (i think (no pun intended))
20 |     prompt #"
21 |         {{ _.role("system") }}
22 | 
23 |         /nothink 
24 | 
25 |         You are a helpful assistant that can help with tasks.
26 | 
27 |         {{ _.role("user") }}
28 | 
29 |         You are working on the following thread:
30 | 
31 |         {{ thread }}
32 | 
33 |         What should the next step be?
34 | 
35 |         {{ ctx.output_format }}
36 |     "#
37 | }
38 | 
39 | test HelloWorld {
40 |   functions [DetermineNextStep]
41 |   args {
42 |     thread #"
43 |       {
44 |         "type": "user_input",
45 |         "data": "hello!"
46 |       }
47 |     "#
48 |   }
49 | }


--------------------------------------------------------------------------------
/workshops/2025-05-17/sections/01-cli-and-agent/walkthrough/01-agent.ts:
--------------------------------------------------------------------------------
 1 | import { b } from "../baml_client";
 2 | 
 3 | // tool call or a respond to human tool
 4 | type AgentResponse = Awaited<ReturnType<typeof b.DetermineNextStep>>;
 5 | 
 6 | export interface Event {
 7 |     type: string
 8 |     data: any;
 9 | }
10 | 
11 | export class Thread {
12 |     events: Event[] = [];
13 | 
14 |     constructor(events: Event[]) {
15 |         this.events = events;
16 |     }
17 | 
18 |     serializeForLLM() {
19 |         // can change this to whatever custom serialization you want to do, XML, etc
20 |         // e.g. https://github.com/got-agents/agents/blob/59ebbfa236fc376618f16ee08eb0f3bf7b698892/linear-assistant-ts/src/agent.ts#L66-L105
21 |         return JSON.stringify(this.events);
22 |     }
23 | }
24 | 
25 | // right now this just runs one turn with the LLM, but
26 | // we'll update this function to handle all the agent logic
27 | export async function agentLoop(thread: Thread): Promise<AgentResponse> {
28 |     const nextStep = await b.DetermineNextStep(thread.serializeForLLM());
29 |     return nextStep;
30 | }
31 | 
32 | 
33 | 


--------------------------------------------------------------------------------
/workshops/2025-05-17/sections/01-cli-and-agent/walkthrough/01-cli.ts:
--------------------------------------------------------------------------------
 1 | // cli.ts lets you invoke the agent loop from the command line
 2 | 
 3 | import { agentLoop, Thread, Event } from "./agent";
 4 | 
 5 | export async function cli() {
 6 |     // Get command line arguments, skipping the first two (node and script name)
 7 |     const args = process.argv.slice(2);
 8 | 
 9 |     if (args.length === 0) {
10 |         console.error("Error: Please provide a message as a command line argument");
11 |         process.exit(1);
12 |     }
13 | 
14 |     // Join all arguments into a single message
15 |     const message = args.join(" ");
16 | 
17 |     // Create a new thread with the user's message as the initial event
18 |     const thread = new Thread([{ type: "user_input", data: message }]);
19 | 
20 |     // Run the agent loop with the thread
21 |     const result = await agentLoop(thread);
22 |     console.log(result);
23 | }
24 | 


--------------------------------------------------------------------------------
/workshops/2025-05-17/sections/01-cli-and-agent/walkthrough/01-index.ts:
--------------------------------------------------------------------------------
 1 | import { cli } from "./cli"
 2 | 
 3 | async function hello(): Promise<void> {
 4 |     console.log('hello, world!')
 5 | }
 6 | 
 7 | async function main() {
 8 |     await cli()
 9 | }
10 | 
11 | main().catch(console.error)


--------------------------------------------------------------------------------
/workshops/2025-05-17/sections/02-calculator-tools/.gitignore:
--------------------------------------------------------------------------------
1 | baml_client/
2 | node_modules/
3 | 


--------------------------------------------------------------------------------
/workshops/2025-05-17/sections/02-calculator-tools/baml_src/agent.baml:
--------------------------------------------------------------------------------
 1 | class DoneForNow {
 2 |   intent "done_for_now"
 3 |   message string 
 4 | }
 5 | 
 6 | client<llm> Qwen3 {
 7 |   provider "openai-generic"
 8 |   options {
 9 |     base_url env.BASETEN_BASE_URL
10 |     api_key env.BASETEN_API_KEY 
11 |   }
12 | }
13 | 
14 | function DetermineNextStep(
15 |     thread: string 
16 | ) -> DoneForNow {
17 |     client Qwen3
18 | 
19 |     // use /nothink for now because the thinking tokens (or streaming thereof) screw with baml (i think (no pun intended))
20 |     prompt #"
21 |         {{ _.role("system") }}
22 | 
23 |         /nothink 
24 | 
25 |         You are a helpful assistant that can help with tasks.
26 | 
27 |         {{ _.role("user") }}
28 | 
29 |         You are working on the following thread:
30 | 
31 |         {{ thread }}
32 | 
33 |         What should the next step be?
34 | 
35 |         {{ ctx.output_format }}
36 |     "#
37 | }
38 | 
39 | test HelloWorld {
40 |   functions [DetermineNextStep]
41 |   args {
42 |     thread #"
43 |       {
44 |         "type": "user_input",
45 |         "data": "hello!"
46 |       }
47 |     "#
48 |   }
49 | }


--------------------------------------------------------------------------------
/workshops/2025-05-17/sections/02-calculator-tools/baml_src/generators.baml:
--------------------------------------------------------------------------------
 1 | // This helps use auto generate libraries you can use in the language of
 2 | // your choice. You can have multiple generators if you use multiple languages.
 3 | // Just ensure that the output_dir is different for each generator.
 4 | generator target {
 5 |     // Valid values: "python/pydantic", "typescript", "ruby/sorbet", "rest/openapi"
 6 |     output_type "typescript"
 7 | 
 8 |     // Where the generated code will be saved (relative to baml_src/)
 9 |     output_dir "../"
10 | 
11 |     // The version of the BAML package you have installed (e.g. same version as your baml-py or @boundaryml/baml).
12 |     // The BAML VSCode extension version should also match this version.
13 |     version "0.88.0"
14 | 
15 |     // Valid values: "sync", "async"
16 |     // This controls what `b.FunctionName()` will be (sync or async).
17 |     default_client_mode async
18 | }
19 | 


--------------------------------------------------------------------------------
/workshops/2025-05-17/sections/02-calculator-tools/src/agent.ts:
--------------------------------------------------------------------------------
 1 | import { b } from "../baml_client";
 2 | 
 3 | // tool call or a respond to human tool
 4 | type AgentResponse = Awaited<ReturnType<typeof b.DetermineNextStep>>;
 5 | 
 6 | export interface Event {
 7 |     type: string
 8 |     data: any;
 9 | }
10 | 
11 | export class Thread {
12 |     events: Event[] = [];
13 | 
14 |     constructor(events: Event[]) {
15 |         this.events = events;
16 |     }
17 | 
18 |     serializeForLLM() {
19 |         // can change this to whatever custom serialization you want to do, XML, etc
20 |         // e.g. https://github.com/got-agents/agents/blob/59ebbfa236fc376618f16ee08eb0f3bf7b698892/linear-assistant-ts/src/agent.ts#L66-L105
21 |         return JSON.stringify(this.events);
22 |     }
23 | }
24 | 
25 | // right now this just runs one turn with the LLM, but
26 | // we'll update this function to handle all the agent logic
27 | export async function agentLoop(thread: Thread): Promise<AgentResponse> {
28 |     const nextStep = await b.DetermineNextStep(thread.serializeForLLM());
29 |     return nextStep;
30 | }
31 | 
32 | 
33 | 


--------------------------------------------------------------------------------
/workshops/2025-05-17/sections/02-calculator-tools/src/cli.ts:
--------------------------------------------------------------------------------
 1 | // cli.ts lets you invoke the agent loop from the command line
 2 | 
 3 | import { agentLoop, Thread, Event } from "./agent";
 4 | 
 5 | export async function cli() {
 6 |     // Get command line arguments, skipping the first two (node and script name)
 7 |     const args = process.argv.slice(2);
 8 | 
 9 |     if (args.length === 0) {
10 |         console.error("Error: Please provide a message as a command line argument");
11 |         process.exit(1);
12 |     }
13 | 
14 |     // Join all arguments into a single message
15 |     const message = args.join(" ");
16 | 
17 |     // Create a new thread with the user's message as the initial event
18 |     const thread = new Thread([{ type: "user_input", data: message }]);
19 | 
20 |     // Run the agent loop with the thread
21 |     const result = await agentLoop(thread);
22 |     console.log(result);
23 | }
24 | 


--------------------------------------------------------------------------------
/workshops/2025-05-17/sections/02-calculator-tools/src/index.ts:
--------------------------------------------------------------------------------
 1 | import { cli } from "./cli"
 2 | 
 3 | async function hello(): Promise<void> {
 4 |     console.log('hello, world!')
 5 | }
 6 | 
 7 | async function main() {
 8 |     await cli()
 9 | }
10 | 
11 | main().catch(console.error)


--------------------------------------------------------------------------------
/workshops/2025-05-17/sections/02-calculator-tools/walkthrough/02-agent.baml:
--------------------------------------------------------------------------------
 1 | class DoneForNow {
 2 |   intent "done_for_now"
 3 |   message string 
 4 | }
 5 | 
 6 | client<llm> Qwen3 {
 7 |   provider "openai-generic"
 8 |   options {
 9 |     base_url env.BASETEN_BASE_URL
10 |     api_key env.BASETEN_API_KEY 
11 |   }
12 | }
13 | 
14 | function DetermineNextStep(
15 |     thread: string 
16 | ) -> CalculatorTools | DoneForNow {
17 |     client Qwen3
18 | 
19 |     // use /nothink for now because the thinking tokens (or streaming thereof) screw with baml (i think (no pun intended))
20 |     prompt #"
21 |         {{ _.role("system") }}
22 | 
23 |         /nothink 
24 | 
25 |         You are a helpful assistant that can help with tasks.
26 | 
27 |         {{ _.role("user") }}
28 | 
29 |         You are working on the following thread:
30 | 
31 |         {{ thread }}
32 | 
33 |         What should the next step be?
34 | 
35 |         {{ ctx.output_format }}
36 |     "#
37 | }
38 | 
39 | test HelloWorld {
40 |   functions [DetermineNextStep]
41 |   args {
42 |     thread #"
43 |       {
44 |         "type": "user_input",
45 |         "data": "hello!"
46 |       }
47 |     "#
48 |   }
49 | }


--------------------------------------------------------------------------------
/workshops/2025-05-17/sections/02-calculator-tools/walkthrough/02-tool_calculator.baml:
--------------------------------------------------------------------------------
 1 | type CalculatorTools = AddTool | SubtractTool | MultiplyTool | DivideTool
 2 | 
 3 | 
 4 | class AddTool {
 5 |     intent "add"
 6 |     a int | float
 7 |     b int | float
 8 | }
 9 | 
10 | class SubtractTool {
11 |     intent "subtract"
12 |     a int | float
13 |     b int | float
14 | }
15 | 
16 | class MultiplyTool {
17 |     intent "multiply"
18 |     a int | float
19 |     b int | float
20 | }
21 | 
22 | class DivideTool {
23 |     intent "divide"
24 |     a int | float
25 |     b int | float
26 | }
27 | 
28 | 


--------------------------------------------------------------------------------
/workshops/2025-05-17/sections/03-tool-loop/.gitignore:
--------------------------------------------------------------------------------
1 | baml_client/
2 | node_modules/
3 | 


--------------------------------------------------------------------------------
/workshops/2025-05-17/sections/03-tool-loop/baml_src/agent.baml:
--------------------------------------------------------------------------------
 1 | class DoneForNow {
 2 |   intent "done_for_now"
 3 |   message string 
 4 | }
 5 | 
 6 | client<llm> Qwen3 {
 7 |   provider "openai-generic"
 8 |   options {
 9 |     base_url env.BASETEN_BASE_URL
10 |     api_key env.BASETEN_API_KEY 
11 |   }
12 | }
13 | 
14 | function DetermineNextStep(
15 |     thread: string 
16 | ) -> CalculatorTools | DoneForNow {
17 |     client Qwen3
18 | 
19 |     // use /nothink for now because the thinking tokens (or streaming thereof) screw with baml (i think (no pun intended))
20 |     prompt #"
21 |         {{ _.role("system") }}
22 | 
23 |         /nothink 
24 | 
25 |         You are a helpful assistant that can help with tasks.
26 | 
27 |         {{ _.role("user") }}
28 | 
29 |         You are working on the following thread:
30 | 
31 |         {{ thread }}
32 | 
33 |         What should the next step be?
34 | 
35 |         {{ ctx.output_format }}
36 |     "#
37 | }
38 | 
39 | test HelloWorld {
40 |   functions [DetermineNextStep]
41 |   args {
42 |     thread #"
43 |       {
44 |         "type": "user_input",
45 |         "data": "hello!"
46 |       }
47 |     "#
48 |   }
49 | }


--------------------------------------------------------------------------------
/workshops/2025-05-17/sections/03-tool-loop/baml_src/clients.baml:
--------------------------------------------------------------------------------
 1 | // Learn more about clients at https://docs.boundaryml.com/docs/snippets/clients/overview
 2 | 
 3 | client<llm> CustomGPT4o {
 4 |   provider openai
 5 |   options {
 6 |     model "gpt-4o"
 7 |     api_key env.OPENAI_API_KEY
 8 |   }
 9 | }
10 | 
11 | client<llm> CustomGPT4oMini {
12 |   provider openai
13 |   retry_policy Exponential
14 |   options {
15 |     model "gpt-4o-mini"
16 |     api_key env.OPENAI_API_KEY
17 |   }
18 | }
19 | 
20 | client<llm> CustomSonnet {
21 |   provider anthropic
22 |   options {
23 |     model "claude-3-5-sonnet-20241022"
24 |     api_key env.ANTHROPIC_API_KEY
25 |   }
26 | }
27 | 
28 | 
29 | client<llm> CustomHaiku {
30 |   provider anthropic
31 |   retry_policy Constant
32 |   options {
33 |     model "claude-3-haiku-20240307"
34 |     api_key env.ANTHROPIC_API_KEY
35 |   }
36 | }
37 | 
38 | // https://docs.boundaryml.com/docs/snippets/clients/round-robin
39 | client<llm> CustomFast {
40 |   provider round-robin
41 |   options {
42 |     // This will alternate between the two clients
43 |     strategy [CustomGPT4oMini, CustomHaiku]
44 |   }
45 | }
46 | 
47 | // https://docs.boundaryml.com/docs/snippets/clients/fallback
48 | client<llm> OpenaiFallback {
49 |   provider fallback
50 |   options {
51 |     // This will try the clients in order until one succeeds
52 |     strategy [CustomGPT4oMini, CustomGPT4oMini]
53 |   }
54 | }
55 | 
56 | // https://docs.boundaryml.com/docs/snippets/clients/retry
57 | retry_policy Constant {
58 |   max_retries 3
59 |   // Strategy is optional
60 |   strategy {
61 |     type constant_delay
62 |     delay_ms 200
63 |   }
64 | }
65 | 
66 | retry_policy Exponential {
67 |   max_retries 2
68 |   // Strategy is optional
69 |   strategy {
70 |     type exponential_backoff
71 |     delay_ms 300
72 |     multiplier 1.5
73 |     max_delay_ms 10000
74 |   }
75 | }


--------------------------------------------------------------------------------
/workshops/2025-05-17/sections/03-tool-loop/baml_src/generators.baml:
--------------------------------------------------------------------------------
 1 | // This helps use auto generate libraries you can use in the language of
 2 | // your choice. You can have multiple generators if you use multiple languages.
 3 | // Just ensure that the output_dir is different for each generator.
 4 | generator target {
 5 |     // Valid values: "python/pydantic", "typescript", "ruby/sorbet", "rest/openapi"
 6 |     output_type "typescript"
 7 | 
 8 |     // Where the generated code will be saved (relative to baml_src/)
 9 |     output_dir "../"
10 | 
11 |     // The version of the BAML package you have installed (e.g. same version as your baml-py or @boundaryml/baml).
12 |     // The BAML VSCode extension version should also match this version.
13 |     version "0.88.0"
14 | 
15 |     // Valid values: "sync", "async"
16 |     // This controls what `b.FunctionName()` will be (sync or async).
17 |     default_client_mode async
18 | }
19 | 


--------------------------------------------------------------------------------
/workshops/2025-05-17/sections/03-tool-loop/baml_src/tool_calculator.baml:
--------------------------------------------------------------------------------
 1 | type CalculatorTools = AddTool | SubtractTool | MultiplyTool | DivideTool
 2 | 
 3 | 
 4 | class AddTool {
 5 |     intent "add"
 6 |     a int | float
 7 |     b int | float
 8 | }
 9 | 
10 | class SubtractTool {
11 |     intent "subtract"
12 |     a int | float
13 |     b int | float
14 | }
15 | 
16 | class MultiplyTool {
17 |     intent "multiply"
18 |     a int | float
19 |     b int | float
20 | }
21 | 
22 | class DivideTool {
23 |     intent "divide"
24 |     a int | float
25 |     b int | float
26 | }
27 | 
28 | 


--------------------------------------------------------------------------------
/workshops/2025-05-17/sections/03-tool-loop/src/agent.ts:
--------------------------------------------------------------------------------
 1 | import { b } from "../baml_client";
 2 | 
 3 | // tool call or a respond to human tool
 4 | type AgentResponse = Awaited<ReturnType<typeof b.DetermineNextStep>>;
 5 | 
 6 | export interface Event {
 7 |     type: string
 8 |     data: any;
 9 | }
10 | 
11 | export class Thread {
12 |     events: Event[] = [];
13 | 
14 |     constructor(events: Event[]) {
15 |         this.events = events;
16 |     }
17 | 
18 |     serializeForLLM() {
19 |         // can change this to whatever custom serialization you want to do, XML, etc
20 |         // e.g. https://github.com/got-agents/agents/blob/59ebbfa236fc376618f16ee08eb0f3bf7b698892/linear-assistant-ts/src/agent.ts#L66-L105
21 |         return JSON.stringify(this.events);
22 |     }
23 | }
24 | 
25 | // right now this just runs one turn with the LLM, but
26 | // we'll update this function to handle all the agent logic
27 | export async function agentLoop(thread: Thread): Promise<AgentResponse> {
28 |     const nextStep = await b.DetermineNextStep(thread.serializeForLLM());
29 |     return nextStep;
30 | }
31 | 
32 | 
33 | 


--------------------------------------------------------------------------------
/workshops/2025-05-17/sections/03-tool-loop/src/cli.ts:
--------------------------------------------------------------------------------
 1 | // cli.ts lets you invoke the agent loop from the command line
 2 | 
 3 | import { agentLoop, Thread, Event } from "./agent";
 4 | 
 5 | export async function cli() {
 6 |     // Get command line arguments, skipping the first two (node and script name)
 7 |     const args = process.argv.slice(2);
 8 | 
 9 |     if (args.length === 0) {
10 |         console.error("Error: Please provide a message as a command line argument");
11 |         process.exit(1);
12 |     }
13 | 
14 |     // Join all arguments into a single message
15 |     const message = args.join(" ");
16 | 
17 |     // Create a new thread with the user's message as the initial event
18 |     const thread = new Thread([{ type: "user_input", data: message }]);
19 | 
20 |     // Run the agent loop with the thread
21 |     const result = await agentLoop(thread);
22 |     console.log(result);
23 | }
24 | 


--------------------------------------------------------------------------------
/workshops/2025-05-17/sections/03-tool-loop/src/index.ts:
--------------------------------------------------------------------------------
 1 | import { cli } from "./cli"
 2 | 
 3 | async function hello(): Promise<void> {
 4 |     console.log('hello, world!')
 5 | }
 6 | 
 7 | async function main() {
 8 |     await cli()
 9 | }
10 | 
11 | main().catch(console.error)


--------------------------------------------------------------------------------
/workshops/2025-05-17/sections/03-tool-loop/walkthrough/03-agent.ts:
--------------------------------------------------------------------------------
 1 | import { b } from "../baml_client";
 2 | 
 3 | // tool call or a respond to human tool
 4 | type AgentResponse = Awaited<ReturnType<typeof b.DetermineNextStep>>;
 5 | 
 6 | export interface Event {
 7 |     type: string
 8 |     data: any;
 9 | }
10 | 
11 | export class Thread {
12 |     events: Event[] = [];
13 | 
14 |     constructor(events: Event[]) {
15 |         this.events = events;
16 |     }
17 | 
18 |     serializeForLLM() {
19 |         // can change this to whatever custom serialization you want to do, XML, etc
20 |         // e.g. https://github.com/got-agents/agents/blob/59ebbfa236fc376618f16ee08eb0f3bf7b698892/linear-assistant-ts/src/agent.ts#L66-L105
21 |         return JSON.stringify(this.events);
22 |     }
23 | }
24 | 
25 | 
26 | 
27 | export async function agentLoop(thread: Thread): Promise<string> {
28 | 
29 |     while (true) {
30 |         const nextStep = await b.DetermineNextStep(thread.serializeForLLM());
31 |         console.log("nextStep", nextStep);
32 | 
33 |         switch (nextStep.intent) {
34 |             case "done_for_now":
35 |                 // response to human, return the next step object
36 |                 return nextStep.message;
37 |             case "add":
38 |                 thread.events.push({
39 |                     "type": "tool_call",
40 |                     "data": nextStep
41 |                 });
42 |                 const result = nextStep.a + nextStep.b;
43 |                 console.log("tool_response", result);
44 |                 thread.events.push({
45 |                     "type": "tool_response",
46 |                     "data": result
47 |                 });
48 |                 continue;
49 |             default:
50 |                 throw new Error(`Unknown intent: ${nextStep.intent}`);
51 |         }
52 |     }
53 | }
54 | 
55 | 
56 | 


--------------------------------------------------------------------------------
/workshops/2025-05-17/tsconfig.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "compilerOptions": {
 3 |       "target": "ES2017",
 4 |       "lib": ["esnext"],
 5 |       "allowJs": true,
 6 |       "skipLibCheck": true,
 7 |       "strict": true,
 8 |       "noEmit": true,
 9 |       "esModuleInterop": true,
10 |       "module": "esnext",
11 |       "moduleResolution": "bundler",
12 |       "resolveJsonModule": true,
13 |       "isolatedModules": true,
14 |       "jsx": "preserve",
15 |       "incremental": true,
16 |       "plugins": [],
17 |       "paths": {
18 |         "@/*": ["./*"]
19 |       }
20 |     },
21 |     "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
22 |     "exclude": ["node_modules", "walkthrough"]
23 |   }
24 |   


--------------------------------------------------------------------------------
/workshops/2025-05-17/walkthrough/00-.gitignore:
--------------------------------------------------------------------------------
1 | baml_client/
2 | node_modules/
3 | 


--------------------------------------------------------------------------------
/workshops/2025-05-17/walkthrough/00-index.ts:
--------------------------------------------------------------------------------
1 | async function hello(): Promise<void> {
2 |     console.log('hello, world!')
3 | }
4 | 
5 | async function main() {
6 |     await hello()
7 | }
8 | 
9 | main().catch(console.error)


--------------------------------------------------------------------------------
/workshops/2025-05-17/walkthrough/00-package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "name": "my-agent",
 3 |     "version": "0.1.0",
 4 |     "private": true,
 5 |     "scripts": {
 6 |       "dev": "tsx src/index.ts",
 7 |       "build": "tsc"
 8 |     },
 9 |     "dependencies": {
10 |       "tsx": "^4.15.0",
11 |       "typescript": "^5.0.0"
12 |     },
13 |     "devDependencies": {
14 |       "@types/node": "^20.0.0",
15 |       "@typescript-eslint/eslint-plugin": "^6.0.0",
16 |       "@typescript-eslint/parser": "^6.0.0",
17 |       "eslint": "^8.0.0"
18 |     }
19 |   }
20 |   


--------------------------------------------------------------------------------
/workshops/2025-05-17/walkthrough/00-tsconfig.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "compilerOptions": {
 3 |       "target": "ES2017",
 4 |       "lib": ["esnext"],
 5 |       "allowJs": true,
 6 |       "skipLibCheck": true,
 7 |       "strict": true,
 8 |       "noEmit": true,
 9 |       "esModuleInterop": true,
10 |       "module": "esnext",
11 |       "moduleResolution": "bundler",
12 |       "resolveJsonModule": true,
13 |       "isolatedModules": true,
14 |       "jsx": "preserve",
15 |       "incremental": true,
16 |       "plugins": [],
17 |       "paths": {
18 |         "@/*": ["./*"]
19 |       }
20 |     },
21 |     "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
22 |     "exclude": ["node_modules", "walkthrough"]
23 |   }
24 |   


--------------------------------------------------------------------------------
/workshops/2025-05-17/walkthrough/01-agent.baml:
--------------------------------------------------------------------------------
 1 | class DoneForNow {
 2 |   intent "done_for_now"
 3 |   message string 
 4 | }
 5 | 
 6 | client<llm> Qwen3 {
 7 |   provider "openai-generic"
 8 |   options {
 9 |     base_url env.BASETEN_BASE_URL
10 |     api_key env.BASETEN_API_KEY 
11 |   }
12 | }
13 | 
14 | function DetermineNextStep(
15 |     thread: string 
16 | ) -> DoneForNow {
17 |     client Qwen3
18 |     // client "openai/gpt-4o"
19 | 
20 |     // use /nothink for now because the thinking tokens (or streaming thereof) screw with baml (i think (no pun intended))
21 |     prompt #"
22 |         {{ _.role("system") }}
23 | 
24 |         /nothink 
25 | 
26 |         You are a helpful assistant that can help with tasks.
27 | 
28 |         {{ _.role("user") }}
29 | 
30 |         You are working on the following thread:
31 | 
32 |         {{ thread }}
33 | 
34 |         What should the next step be?
35 | 
36 |         {{ ctx.output_format }}
37 |     "#
38 | }
39 | 
40 | test HelloWorld {
41 |   functions [DetermineNextStep]
42 |   args {
43 |     thread #"
44 |       {
45 |         "type": "user_input",
46 |         "data": "hello!"
47 |       }
48 |     "#
49 |   }
50 | }


--------------------------------------------------------------------------------
/workshops/2025-05-17/walkthrough/01-agent.ts:
--------------------------------------------------------------------------------
 1 | import { b } from "../baml_client";
 2 | 
 3 | // tool call or a respond to human tool
 4 | type AgentResponse = Awaited<ReturnType<typeof b.DetermineNextStep>>;
 5 | 
 6 | export interface Event {
 7 |     type: string
 8 |     data: any;
 9 | }
10 | 
11 | export class Thread {
12 |     events: Event[] = [];
13 | 
14 |     constructor(events: Event[]) {
15 |         this.events = events;
16 |     }
17 | 
18 |     serializeForLLM() {
19 |         // can change this to whatever custom serialization you want to do, XML, etc
20 |         // e.g. https://github.com/got-agents/agents/blob/59ebbfa236fc376618f16ee08eb0f3bf7b698892/linear-assistant-ts/src/agent.ts#L66-L105
21 |         return JSON.stringify(this.events);
22 |     }
23 | }
24 | 
25 | // right now this just runs one turn with the LLM, but
26 | // we'll update this function to handle all the agent logic
27 | export async function agentLoop(thread: Thread): Promise<AgentResponse> {
28 |     const nextStep = await b.DetermineNextStep(thread.serializeForLLM());
29 |     return nextStep;
30 | }
31 | 
32 | 
33 | 


--------------------------------------------------------------------------------
/workshops/2025-05-17/walkthrough/01-cli.ts:
--------------------------------------------------------------------------------
 1 | // cli.ts lets you invoke the agent loop from the command line
 2 | 
 3 | import { agentLoop, Thread, Event } from "./agent";
 4 | 
 5 | export async function cli() {
 6 |     // Get command line arguments, skipping the first two (node and script name)
 7 |     const args = process.argv.slice(2);
 8 | 
 9 |     if (args.length === 0) {
10 |         console.error("Error: Please provide a message as a command line argument");
11 |         process.exit(1);
12 |     }
13 | 
14 |     // Join all arguments into a single message
15 |     const message = args.join(" ");
16 | 
17 |     // Create a new thread with the user's message as the initial event
18 |     const thread = new Thread([{ type: "user_input", data: message }]);
19 | 
20 |     // Run the agent loop with the thread
21 |     const result = await agentLoop(thread);
22 |     console.log(result);
23 | }
24 | 


--------------------------------------------------------------------------------
/workshops/2025-05-17/walkthrough/01-index.ts:
--------------------------------------------------------------------------------
 1 | import { cli } from "./cli"
 2 | 
 3 | async function hello(): Promise<void> {
 4 |     console.log('hello, world!')
 5 | }
 6 | 
 7 | async function main() {
 8 |     await cli()
 9 | }
10 | 
11 | main().catch(console.error)


--------------------------------------------------------------------------------
/workshops/2025-05-17/walkthrough/02-agent.baml:
--------------------------------------------------------------------------------
 1 | class DoneForNow {
 2 |   intent "done_for_now"
 3 |   message string 
 4 | }
 5 | 
 6 | client<llm> Qwen3 {
 7 |   provider "openai-generic"
 8 |   options {
 9 |     base_url env.BASETEN_BASE_URL
10 |     api_key env.BASETEN_API_KEY 
11 |   }
12 | }
13 | 
14 | function DetermineNextStep(
15 |     thread: string 
16 | ) -> CalculatorTools | DoneForNow {
17 |     client Qwen3
18 | 
19 |     // client "openai/gpt-4o"
20 | 
21 |     // use /nothink for now because the thinking tokens (or streaming thereof) screw with baml (i think (no pun intended))
22 |     prompt #"
23 |         {{ _.role("system") }}
24 | 
25 |         /nothink 
26 | 
27 |         You are a helpful assistant that can help with tasks.
28 | 
29 |         {{ _.role("user") }}
30 | 
31 |         You are working on the following thread:
32 | 
33 |         {{ thread }}
34 | 
35 |         What should the next step be?
36 | 
37 |         {{ ctx.output_format }}
38 |     "#
39 | }
40 | 
41 | test HelloWorld {
42 |   functions [DetermineNextStep]
43 |   args {
44 |     thread #"
45 |       {
46 |         "type": "user_input",
47 |         "data": "hello!"
48 |       }
49 |     "#
50 |   }
51 | }


--------------------------------------------------------------------------------
/workshops/2025-05-17/walkthrough/02-tool_calculator.baml:
--------------------------------------------------------------------------------
 1 | type CalculatorTools = AddTool | SubtractTool | MultiplyTool | DivideTool
 2 | 
 3 | 
 4 | class AddTool {
 5 |     intent "add"
 6 |     a int | float
 7 |     b int | float
 8 | }
 9 | 
10 | class SubtractTool {
11 |     intent "subtract"
12 |     a int | float
13 |     b int | float
14 | }
15 | 
16 | class MultiplyTool {
17 |     intent "multiply"
18 |     a int | float
19 |     b int | float
20 | }
21 | 
22 | class DivideTool {
23 |     intent "divide"
24 |     a int | float
25 |     b int | float
26 | }
27 | 
28 | 


--------------------------------------------------------------------------------
/workshops/2025-05-17/walkthrough/03-agent.ts:
--------------------------------------------------------------------------------
 1 | import { b } from "../baml_client";
 2 | 
 3 | // tool call or a respond to human tool
 4 | type AgentResponse = Awaited<ReturnType<typeof b.DetermineNextStep>>;
 5 | 
 6 | export interface Event {
 7 |     type: string
 8 |     data: any;
 9 | }
10 | 
11 | export class Thread {
12 |     events: Event[] = [];
13 | 
14 |     constructor(events: Event[]) {
15 |         this.events = events;
16 |     }
17 | 
18 |     serializeForLLM() {
19 |         // can change this to whatever custom serialization you want to do, XML, etc
20 |         // e.g. https://github.com/got-agents/agents/blob/59ebbfa236fc376618f16ee08eb0f3bf7b698892/linear-assistant-ts/src/agent.ts#L66-L105
21 |         return JSON.stringify(this.events);
22 |     }
23 | }
24 | 
25 | 
26 | 
27 | export async function agentLoop(thread: Thread): Promise<string> {
28 | 
29 |     while (true) {
30 |         const nextStep = await b.DetermineNextStep(thread.serializeForLLM());
31 |         console.log("nextStep", nextStep);
32 | 
33 |         switch (nextStep.intent) {
34 |             case "done_for_now":
35 |                 // response to human, return the next step object
36 |                 return nextStep.message;
37 |             case "add":
38 |                 thread.events.push({
39 |                     "type": "tool_call",
40 |                     "data": nextStep
41 |                 });
42 |                 const result = nextStep.a + nextStep.b;
43 |                 console.log("tool_response", result);
44 |                 thread.events.push({
45 |                     "type": "tool_response",
46 |                     "data": result
47 |                 });
48 |                 continue;
49 |             default:
50 |                 throw new Error(`Unknown intent: ${nextStep.intent}`);
51 |         }
52 |     }
53 | }
54 | 
55 | 
56 | 


--------------------------------------------------------------------------------
/workshops/2025-05-17/walkthrough/04-agent.baml:
--------------------------------------------------------------------------------
 1 | class DoneForNow {
 2 |   intent "done_for_now"
 3 |   message string 
 4 | }
 5 | 
 6 | client<llm> Qwen3 {
 7 |   provider "openai-generic"
 8 |   options {
 9 |     base_url env.BASETEN_BASE_URL
10 |     api_key env.BASETEN_API_KEY 
11 |   }
12 | }
13 | 
14 | function DetermineNextStep(
15 |     thread: string 
16 | ) -> CalculatorTools | DoneForNow {
17 |     client Qwen3
18 |     // client "openai/gpt-4o"
19 | 
20 |     prompt #"
21 |         {{ _.role("system") }}
22 | 
23 |         /nothink
24 | 
25 |         You are a helpful assistant that can help with tasks.
26 | 
27 |         {{ _.role("user") }}
28 | 
29 |         You are working on the following thread:
30 | 
31 |         {{ thread }}
32 | 
33 |         What should the next step be?
34 | 
35 |         {{ ctx.output_format }}
36 |     "#
37 | }
38 | 
39 | test HelloWorld {
40 |   functions [DetermineNextStep]
41 |   args {
42 |     thread #"
43 |       {
44 |         "type": "user_input",
45 |         "data": "hello!"
46 |       }
47 |     "#
48 |   }
49 | }
50 | 
51 | test MathOperation {
52 |   functions [DetermineNextStep]
53 |   args {
54 |     thread #"
55 |       {
56 |         "type": "user_input",
57 |         "data": "can you multiply 3 and 4?"
58 |       }
59 |     "#
60 |   }
61 | }
62 | 
63 | 


--------------------------------------------------------------------------------
/workshops/2025-05-17/walkthrough/04b-agent.baml:
--------------------------------------------------------------------------------
 1 | class DoneForNow {
 2 |   intent "done_for_now"
 3 |   message string 
 4 | }
 5 | 
 6 | client<llm> Qwen3 {
 7 |   provider "openai-generic"
 8 |   options {
 9 |     base_url env.BASETEN_BASE_URL
10 |     api_key env.BASETEN_API_KEY 
11 |   }
12 | }
13 | 
14 | function DetermineNextStep(
15 |     thread: string 
16 | ) -> CalculatorTools | DoneForNow {
17 |     client Qwen3
18 |     // client "openai/gpt-4o" 
19 | 
20 |     prompt #"
21 |         {{ _.role("system") }}
22 | 
23 |         /nothink
24 | 
25 |         You are a helpful assistant that can help with tasks.
26 | 
27 |         {{ _.role("user") }}
28 | 
29 |         You are working on the following thread:
30 | 
31 |         {{ thread }}
32 | 
33 |         What should the next step be?
34 | 
35 |         {{ ctx.output_format }}
36 |     "#
37 | }
38 | 
39 | test HelloWorld {
40 |   functions [DetermineNextStep]
41 |   args {
42 |     thread #"
43 |       {
44 |         "type": "user_input",
45 |         "data": "hello!"
46 |       }
47 |     "#
48 |   }
49 |   @@assert(hello, {{this.intent == "done_for_now"}})
50 | }
51 | 
52 | test MathOperation {
53 |   functions [DetermineNextStep]
54 |   args {
55 |     thread #"
56 |       {
57 |         "type": "user_input",
58 |         "data": "can you multiply 3 and 4?"
59 |       }
60 |     "#
61 |   }
62 |   @@assert(math_operation, {{this.intent == "multiply"}})
63 | }
64 | 
65 | 


--------------------------------------------------------------------------------
/workshops/2025-05-17/walkthrough/05-cli.ts:
--------------------------------------------------------------------------------
 1 | // cli.ts lets you invoke the agent loop from the command line
 2 | 
 3 | import { agentLoop, Thread, Event } from "../src/agent";
 4 | 
 5 | 
 6 | 
 7 | export async function cli() {
 8 |     // Get command line arguments, skipping the first two (node and script name)
 9 |     const args = process.argv.slice(2);
10 | 
11 |     if (args.length === 0) {
12 |         console.error("Error: Please provide a message as a command line argument");
13 |         process.exit(1);
14 |     }
15 | 
16 |     // Join all arguments into a single message
17 |     const message = args.join(" ");
18 | 
19 |     // Create a new thread with the user's message as the initial event
20 |     const thread = new Thread([{ type: "user_input", data: message }]);
21 | 
22 |     // Run the agent loop with the thread
23 |     const result = await agentLoop(thread);
24 |     let lastEvent = result.events.slice(-1)[0];
25 | 
26 |     while (lastEvent.data.intent === "request_more_information") {
27 |         const message = await askHuman(lastEvent.data.message);
28 |         thread.events.push({ type: "human_response", data: message });
29 |         const result = await agentLoop(thread);
30 |         lastEvent = result.events.slice(-1)[0];
31 |     }
32 | 
33 |     // print the final result
34 |     // optional - you could loop here too
35 |     console.log(lastEvent.data.message);
36 |     process.exit(0);
37 | }
38 | 
39 | async function askHuman(message: string) {
40 |     const readline = require('readline').createInterface({
41 |         input: process.stdin,
42 |         output: process.stdout
43 |     });
44 | 
45 |     return new Promise((resolve) => {
46 |         readline.question(`${message}\n> `, (answer: string) => {
47 |             resolve(answer);
48 |         });
49 |     });
50 | }
51 | 


--------------------------------------------------------------------------------
/workshops/2025-05-17/walkthrough/08-server.ts:
--------------------------------------------------------------------------------
 1 | import express from 'express';
 2 | import { Thread, agentLoop } from '../src/agent';
 3 | 
 4 | const app = express();
 5 | app.use(express.json());
 6 | app.set('json spaces', 2);
 7 | 
 8 | // POST /thread - Start new thread
 9 | app.post('/thread', async (req, res) => {
10 |     const thread = new Thread([{
11 |         type: "user_input",
12 |         data: req.body.message
13 |     }]);
14 |     const result = await agentLoop(thread);
15 |     res.json(result);
16 | });
17 | 
18 | // GET /thread/:id - Get thread status 
19 | app.get('/thread/:id', (req, res) => {
20 |     // optional - add state
21 |     res.status(404).json({ error: "Not implemented yet" });
22 | });
23 | 
24 | const port = process.env.PORT || 3000;
25 | app.listen(port, () => {
26 |     console.log(`Server running on port ${port}`);
27 | });
28 | 
29 | export { app };


--------------------------------------------------------------------------------
/workshops/2025-05-17/walkthrough/09-state.ts:
--------------------------------------------------------------------------------
 1 | import crypto from 'crypto';
 2 | import { Thread } from '../src/agent';
 3 | 
 4 | 
 5 | // you can replace this with any simple state management,
 6 | // e.g. redis, sqlite, postgres, etc
 7 | export class ThreadStore {
 8 |     private threads: Map<string, Thread> = new Map();
 9 |     
10 |     create(thread: Thread): string {
11 |         const id = crypto.randomUUID();
12 |         this.threads.set(id, thread);
13 |         return id;
14 |     }
15 |     
16 |     get(id: string): Thread | undefined {
17 |         return this.threads.get(id);
18 |     }
19 |     
20 |     update(id: string, thread: Thread): void {
21 |         this.threads.set(id, thread);
22 |     }
23 | }


--------------------------------------------------------------------------------
/workshops/2025-05-17/walkthrough/11-email-approve.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humanlayer/12-factor-agents/d20c728368bf9c189d6d7aab704744decb6ec0cc/workshops/2025-05-17/walkthrough/11-email-approve.png


--------------------------------------------------------------------------------
/workshops/2025-05-17/walkthrough/11-email-custom.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humanlayer/12-factor-agents/d20c728368bf9c189d6d7aab704744decb6ec0cc/workshops/2025-05-17/walkthrough/11-email-custom.png


--------------------------------------------------------------------------------
/workshops/2025-05-17/walkthrough/11-email-reject.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humanlayer/12-factor-agents/d20c728368bf9c189d6d7aab704744decb6ec0cc/workshops/2025-05-17/walkthrough/11-email-reject.png


--------------------------------------------------------------------------------
/workshops/2025-05/.gitignore:
--------------------------------------------------------------------------------
1 | build/
2 | 


--------------------------------------------------------------------------------
/workshops/2025-05/Makefile:
--------------------------------------------------------------------------------
 1 | .PHONY: clean
 2 | clean:
 3 | 	rm -rf build/
 4 | 
 5 | .PHONY: generate
 6 | generate: clean
 7 | 	npm -C ../../packages/walkthroughgen/ \
 8 | 	  exec tsx \
 9 | 	  ../../packages/walkthroughgen/src/index.ts \
10 | 	  generate walkthrough.yaml	
11 |   
12 | 


--------------------------------------------------------------------------------
/workshops/2025-05/final/.gitignore:
--------------------------------------------------------------------------------
1 | baml_client/
2 | node_modules/
3 | 


--------------------------------------------------------------------------------
/workshops/2025-05/final/baml_src/clients.baml:
--------------------------------------------------------------------------------
 1 | // Learn more about clients at https://docs.boundaryml.com/docs/snippets/clients/overview
 2 | 
 3 | client<llm> CustomGPT4o {
 4 |   provider openai
 5 |   options {
 6 |     model "gpt-4o"
 7 |     api_key env.OPENAI_API_KEY
 8 |   }
 9 | }
10 | 
11 | client<llm> CustomGPT4oMini {
12 |   provider openai
13 |   retry_policy Exponential
14 |   options {
15 |     model "gpt-4o-mini"
16 |     api_key env.OPENAI_API_KEY
17 |   }
18 | }
19 | 
20 | client<llm> CustomSonnet {
21 |   provider anthropic
22 |   options {
23 |     model "claude-3-5-sonnet-20241022"
24 |     api_key env.ANTHROPIC_API_KEY
25 |   }
26 | }
27 | 
28 | 
29 | client<llm> CustomHaiku {
30 |   provider anthropic
31 |   retry_policy Constant
32 |   options {
33 |     model "claude-3-haiku-20240307"
34 |     api_key env.ANTHROPIC_API_KEY
35 |   }
36 | }
37 | 
38 | // https://docs.boundaryml.com/docs/snippets/clients/round-robin
39 | client<llm> CustomFast {
40 |   provider round-robin
41 |   options {
42 |     // This will alternate between the two clients
43 |     strategy [CustomGPT4oMini, CustomHaiku]
44 |   }
45 | }
46 | 
47 | // https://docs.boundaryml.com/docs/snippets/clients/fallback
48 | client<llm> OpenaiFallback {
49 |   provider fallback
50 |   options {
51 |     // This will try the clients in order until one succeeds
52 |     strategy [CustomGPT4oMini, CustomGPT4oMini]
53 |   }
54 | }
55 | 
56 | // https://docs.boundaryml.com/docs/snippets/clients/retry
57 | retry_policy Constant {
58 |   max_retries 3
59 |   // Strategy is optional
60 |   strategy {
61 |     type constant_delay
62 |     delay_ms 200
63 |   }
64 | }
65 | 
66 | retry_policy Exponential {
67 |   max_retries 2
68 |   // Strategy is optional
69 |   strategy {
70 |     type exponential_backoff
71 |     delay_ms 300
72 |     multiplier 1.5
73 |     max_delay_ms 10000
74 |   }
75 | }


--------------------------------------------------------------------------------
/workshops/2025-05/final/baml_src/generators.baml:
--------------------------------------------------------------------------------
 1 | // This helps use auto generate libraries you can use in the language of
 2 | // your choice. You can have multiple generators if you use multiple languages.
 3 | // Just ensure that the output_dir is different for each generator.
 4 | generator target {
 5 |     // Valid values: "python/pydantic", "typescript", "ruby/sorbet", "rest/openapi"
 6 |     output_type "typescript"
 7 | 
 8 |     // Where the generated code will be saved (relative to baml_src/)
 9 |     output_dir "../"
10 | 
11 |     // The version of the BAML package you have installed (e.g. same version as your baml-py or @boundaryml/baml).
12 |     // The BAML VSCode extension version should also match this version.
13 |     version "0.85.0"
14 | 
15 |     // Valid values: "sync", "async"
16 |     // This controls what `b.FunctionName()` will be (sync or async).
17 |     default_client_mode async
18 | }
19 | 


--------------------------------------------------------------------------------
/workshops/2025-05/final/baml_src/tool_calculator.baml:
--------------------------------------------------------------------------------
 1 | type CalculatorTools = AddTool | SubtractTool | MultiplyTool | DivideTool
 2 | 
 3 | 
 4 | class AddTool {
 5 |     intent "add"
 6 |     a int | float
 7 |     b int | float
 8 | }
 9 | 
10 | class SubtractTool {
11 |     intent "subtract"
12 |     a int | float
13 |     b int | float
14 | }
15 | 
16 | class MultiplyTool {
17 |     intent "multiply"
18 |     a int | float
19 |     b int | float
20 | }
21 | 
22 | class DivideTool {
23 |     intent "divide"
24 |     a int | float
25 |     b int | float
26 | }
27 | 
28 | 


--------------------------------------------------------------------------------
/workshops/2025-05/final/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "name": "my-agent",
 3 |     "version": "0.1.0",
 4 |     "private": true,
 5 |     "scripts": {
 6 |         "dev": "tsx src/index.ts",
 7 |         "build": "tsc"
 8 |     },
 9 |     "dependencies": {
10 |         "baml": "^0.0.0",
11 |         "express": "^5.1.0",
12 |         "humanlayer": "^0.7.7",
13 |         "tsx": "^4.15.0",
14 |         "typescript": "^5.0.0"
15 |     },
16 |     "devDependencies": {
17 |         "@types/express": "^5.0.1",
18 |         "@types/node": "^20.0.0",
19 |         "@typescript-eslint/eslint-plugin": "^6.0.0",
20 |         "@typescript-eslint/parser": "^6.0.0",
21 |         "eslint": "^8.0.0",
22 |         "supertest": "^7.1.0"
23 |     }
24 | }
25 | 


--------------------------------------------------------------------------------
/workshops/2025-05/final/src/index.ts:
--------------------------------------------------------------------------------
 1 | import { cli } from "./cli"
 2 | 
 3 | async function hello(): Promise<void> {
 4 |     console.log('hello, world!')
 5 | }
 6 | 
 7 | async function main() {
 8 |     await cli()
 9 | }
10 | 
11 | main().catch(console.error)


--------------------------------------------------------------------------------
/workshops/2025-05/final/src/state.ts:
--------------------------------------------------------------------------------
 1 | import crypto from 'crypto';
 2 | import { Thread } from '../src/agent';
 3 | 
 4 | 
 5 | // you can replace this with any simple state management,
 6 | // e.g. redis, sqlite, postgres, etc
 7 | export class ThreadStore {
 8 |     private threads: Map<string, Thread> = new Map();
 9 |     
10 |     create(thread: Thread): string {
11 |         const id = crypto.randomUUID();
12 |         this.threads.set(id, thread);
13 |         return id;
14 |     }
15 |     
16 |     get(id: string): Thread | undefined {
17 |         return this.threads.get(id);
18 |     }
19 |     
20 |     update(id: string, thread: Thread): void {
21 |         this.threads.set(id, thread);
22 |     }
23 | }


--------------------------------------------------------------------------------
/workshops/2025-05/final/tsconfig.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "compilerOptions": {
 3 |       "target": "ES2017",
 4 |       "lib": ["esnext"],
 5 |       "allowJs": true,
 6 |       "skipLibCheck": true,
 7 |       "strict": true,
 8 |       "noEmit": true,
 9 |       "esModuleInterop": true,
10 |       "module": "esnext",
11 |       "moduleResolution": "bundler",
12 |       "resolveJsonModule": true,
13 |       "isolatedModules": true,
14 |       "jsx": "preserve",
15 |       "incremental": true,
16 |       "plugins": [],
17 |       "paths": {
18 |         "@/*": ["./*"]
19 |       }
20 |     },
21 |     "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
22 |     "exclude": ["node_modules", "walkthrough"]
23 |   }
24 |   


--------------------------------------------------------------------------------
/workshops/2025-05/sections/00-hello-world/walkthrough/00-.gitignore:
--------------------------------------------------------------------------------
1 | baml_client/
2 | node_modules/
3 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/00-hello-world/walkthrough/00-index.ts:
--------------------------------------------------------------------------------
1 | async function hello(): Promise<void> {
2 |     console.log('hello, world!')
3 | }
4 | 
5 | async function main() {
6 |     await hello()
7 | }
8 | 
9 | main().catch(console.error)


--------------------------------------------------------------------------------
/workshops/2025-05/sections/00-hello-world/walkthrough/00-package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "name": "my-agent",
 3 |     "version": "0.1.0",
 4 |     "private": true,
 5 |     "scripts": {
 6 |       "dev": "tsx src/index.ts",
 7 |       "build": "tsc"
 8 |     },
 9 |     "dependencies": {
10 |       "tsx": "^4.15.0",
11 |       "typescript": "^5.0.0"
12 |     },
13 |     "devDependencies": {
14 |       "@types/node": "^20.0.0",
15 |       "@typescript-eslint/eslint-plugin": "^6.0.0",
16 |       "@typescript-eslint/parser": "^6.0.0",
17 |       "eslint": "^8.0.0"
18 |     }
19 |   }
20 |   


--------------------------------------------------------------------------------
/workshops/2025-05/sections/00-hello-world/walkthrough/00-tsconfig.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "compilerOptions": {
 3 |       "target": "ES2017",
 4 |       "lib": ["esnext"],
 5 |       "allowJs": true,
 6 |       "skipLibCheck": true,
 7 |       "strict": true,
 8 |       "noEmit": true,
 9 |       "esModuleInterop": true,
10 |       "module": "esnext",
11 |       "moduleResolution": "bundler",
12 |       "resolveJsonModule": true,
13 |       "isolatedModules": true,
14 |       "jsx": "preserve",
15 |       "incremental": true,
16 |       "plugins": [],
17 |       "paths": {
18 |         "@/*": ["./*"]
19 |       }
20 |     },
21 |     "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
22 |     "exclude": ["node_modules", "walkthrough"]
23 |   }
24 |   


--------------------------------------------------------------------------------
/workshops/2025-05/sections/01-cli-and-agent/.gitignore:
--------------------------------------------------------------------------------
1 | baml_client/
2 | node_modules/
3 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/01-cli-and-agent/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "name": "my-agent",
 3 |     "version": "0.1.0",
 4 |     "private": true,
 5 |     "scripts": {
 6 |       "dev": "tsx src/index.ts",
 7 |       "build": "tsc"
 8 |     },
 9 |     "dependencies": {
10 |       "tsx": "^4.15.0",
11 |       "typescript": "^5.0.0"
12 |     },
13 |     "devDependencies": {
14 |       "@types/node": "^20.0.0",
15 |       "@typescript-eslint/eslint-plugin": "^6.0.0",
16 |       "@typescript-eslint/parser": "^6.0.0",
17 |       "eslint": "^8.0.0"
18 |     }
19 |   }
20 |   


--------------------------------------------------------------------------------
/workshops/2025-05/sections/01-cli-and-agent/src/index.ts:
--------------------------------------------------------------------------------
1 | async function hello(): Promise<void> {
2 |     console.log('hello, world!')
3 | }
4 | 
5 | async function main() {
6 |     await hello()
7 | }
8 | 
9 | main().catch(console.error)


--------------------------------------------------------------------------------
/workshops/2025-05/sections/01-cli-and-agent/tsconfig.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "compilerOptions": {
 3 |       "target": "ES2017",
 4 |       "lib": ["esnext"],
 5 |       "allowJs": true,
 6 |       "skipLibCheck": true,
 7 |       "strict": true,
 8 |       "noEmit": true,
 9 |       "esModuleInterop": true,
10 |       "module": "esnext",
11 |       "moduleResolution": "bundler",
12 |       "resolveJsonModule": true,
13 |       "isolatedModules": true,
14 |       "jsx": "preserve",
15 |       "incremental": true,
16 |       "plugins": [],
17 |       "paths": {
18 |         "@/*": ["./*"]
19 |       }
20 |     },
21 |     "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
22 |     "exclude": ["node_modules", "walkthrough"]
23 |   }
24 |   


--------------------------------------------------------------------------------
/workshops/2025-05/sections/01-cli-and-agent/walkthrough/01-agent.baml:
--------------------------------------------------------------------------------
 1 | class DoneForNow {
 2 |   intent "done_for_now"
 3 |   message string 
 4 | }
 5 | 
 6 | function DetermineNextStep(
 7 |     thread: string 
 8 | ) -> DoneForNow {
 9 |     client "openai/gpt-4o"
10 | 
11 |     prompt #"
12 |         {{ _.role("system") }}
13 | 
14 |         You are a helpful assistant that can help with tasks.
15 | 
16 |         {{ _.role("user") }}
17 | 
18 |         You are working on the following thread:
19 | 
20 |         {{ thread }}
21 | 
22 |         What should the next step be?
23 | 
24 |         {{ ctx.output_format }}
25 |     "#
26 | }
27 | 
28 | test HelloWorld {
29 |   functions [DetermineNextStep]
30 |   args {
31 |     thread #"
32 |       {
33 |         "type": "user_input",
34 |         "data": "hello!"
35 |       }
36 |     "#
37 |   }
38 | }


--------------------------------------------------------------------------------
/workshops/2025-05/sections/01-cli-and-agent/walkthrough/01-agent.ts:
--------------------------------------------------------------------------------
 1 | import { b } from "../baml_client";
 2 | 
 3 | // tool call or a respond to human tool
 4 | type AgentResponse = Awaited<ReturnType<typeof b.DetermineNextStep>>;
 5 | 
 6 | export interface Event {
 7 |     type: string
 8 |     data: any;
 9 | }
10 | 
11 | export class Thread {
12 |     events: Event[] = [];
13 | 
14 |     constructor(events: Event[]) {
15 |         this.events = events;
16 |     }
17 | 
18 |     serializeForLLM() {
19 |         // can change this to whatever custom serialization you want to do, XML, etc
20 |         // e.g. https://github.com/got-agents/agents/blob/59ebbfa236fc376618f16ee08eb0f3bf7b698892/linear-assistant-ts/src/agent.ts#L66-L105
21 |         return JSON.stringify(this.events);
22 |     }
23 | }
24 | 
25 | // right now this just runs one turn with the LLM, but
26 | // we'll update this function to handle all the agent logic
27 | export async function agentLoop(thread: Thread): Promise<AgentResponse> {
28 |     const nextStep = await b.DetermineNextStep(thread.serializeForLLM());
29 |     return nextStep;
30 | }
31 | 
32 | 
33 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/01-cli-and-agent/walkthrough/01-cli.ts:
--------------------------------------------------------------------------------
 1 | // cli.ts lets you invoke the agent loop from the command line
 2 | 
 3 | import { agentLoop, Thread, Event } from "./agent";
 4 | 
 5 | export async function cli() {
 6 |     // Get command line arguments, skipping the first two (node and script name)
 7 |     const args = process.argv.slice(2);
 8 | 
 9 |     if (args.length === 0) {
10 |         console.error("Error: Please provide a message as a command line argument");
11 |         process.exit(1);
12 |     }
13 | 
14 |     // Join all arguments into a single message
15 |     const message = args.join(" ");
16 | 
17 |     // Create a new thread with the user's message as the initial event
18 |     const thread = new Thread([{ type: "user_input", data: message }]);
19 | 
20 |     // Run the agent loop with the thread
21 |     const result = await agentLoop(thread);
22 |     console.log(result);
23 | }
24 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/01-cli-and-agent/walkthrough/01-index.ts:
--------------------------------------------------------------------------------
 1 | import { cli } from "./cli"
 2 | 
 3 | async function hello(): Promise<void> {
 4 |     console.log('hello, world!')
 5 | }
 6 | 
 7 | async function main() {
 8 |     await cli()
 9 | }
10 | 
11 | main().catch(console.error)


--------------------------------------------------------------------------------
/workshops/2025-05/sections/02-calculator-tools/.gitignore:
--------------------------------------------------------------------------------
1 | baml_client/
2 | node_modules/
3 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/02-calculator-tools/README.md:
--------------------------------------------------------------------------------
 1 | # Chapter 2 - Add Calculator Tools
 2 | 
 3 | Let's add some calculator tools to our agent.
 4 | 
 5 | Let's start by adding a tool definition for the calculator
 6 | 
 7 | These are simpile structured outputs that we'll ask the model to 
 8 | return as a "next step" in the agentic loop.
 9 | 
10 | 
11 |     cp ./walkthrough/02-tool_calculator.baml baml_src/tool_calculator.baml
12 | 
13 | <details>
14 | <summary>show file</summary>
15 | 
16 | ```rust
17 | // ./walkthrough/02-tool_calculator.baml
18 | type CalculatorTools = AddTool | SubtractTool | MultiplyTool | DivideTool
19 | 
20 | 
21 | class AddTool {
22 |     intent "add"
23 |     a int | float
24 |     b int | float
25 | }
26 | 
27 | class SubtractTool {
28 |     intent "subtract"
29 |     a int | float
30 |     b int | float
31 | }
32 | 
33 | class MultiplyTool {
34 |     intent "multiply"
35 |     a int | float
36 |     b int | float
37 | }
38 | 
39 | class DivideTool {
40 |     intent "divide"
41 |     a int | float
42 |     b int | float
43 | }
44 | ```
45 | 
46 | </details>
47 | 
48 | Now, let's update the agent's DetermineNextStep method to
49 | expose the calculator tools as potential next steps
50 | 
51 | 
52 | ```diff
53 | baml_src/agent.baml
54 |  function DetermineNextStep(
55 |      thread: string 
56 | -) -> DoneForNow {
57 | +) -> CalculatorTools | DoneForNow {
58 |      client "openai/gpt-4o"
59 |  
60 | ```
61 | 
62 | <details>
63 | <summary>skip this step</summary>
64 | 
65 |     cp ./walkthrough/02-agent.baml baml_src/agent.baml
66 | 
67 | </details>
68 | 
69 | Generate updated BAML client
70 | 
71 |     npx baml-cli generate
72 | 
73 | Try out the calculator
74 | 
75 |     npx tsx src/index.ts 'can you add 3 and 4'
76 | 
77 | You should see a tool call to the calculator
78 | 
79 |     {
80 |   intent: 'add',
81 |   a: 3,
82 |   b: 4
83 | }
84 | 
85 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/02-calculator-tools/baml_src/agent.baml:
--------------------------------------------------------------------------------
 1 | class DoneForNow {
 2 |   intent "done_for_now"
 3 |   message string 
 4 | }
 5 | 
 6 | function DetermineNextStep(
 7 |     thread: string 
 8 | ) -> DoneForNow {
 9 |     client "openai/gpt-4o"
10 | 
11 |     prompt #"
12 |         {{ _.role("system") }}
13 | 
14 |         You are a helpful assistant that can help with tasks.
15 | 
16 |         {{ _.role("user") }}
17 | 
18 |         You are working on the following thread:
19 | 
20 |         {{ thread }}
21 | 
22 |         What should the next step be?
23 | 
24 |         {{ ctx.output_format }}
25 |     "#
26 | }
27 | 
28 | test HelloWorld {
29 |   functions [DetermineNextStep]
30 |   args {
31 |     thread #"
32 |       {
33 |         "type": "user_input",
34 |         "data": "hello!"
35 |       }
36 |     "#
37 |   }
38 | }


--------------------------------------------------------------------------------
/workshops/2025-05/sections/02-calculator-tools/baml_src/generators.baml:
--------------------------------------------------------------------------------
 1 | // This helps use auto generate libraries you can use in the language of
 2 | // your choice. You can have multiple generators if you use multiple languages.
 3 | // Just ensure that the output_dir is different for each generator.
 4 | generator target {
 5 |     // Valid values: "python/pydantic", "typescript", "ruby/sorbet", "rest/openapi"
 6 |     output_type "typescript"
 7 | 
 8 |     // Where the generated code will be saved (relative to baml_src/)
 9 |     output_dir "../"
10 | 
11 |     // The version of the BAML package you have installed (e.g. same version as your baml-py or @boundaryml/baml).
12 |     // The BAML VSCode extension version should also match this version.
13 |     version "0.85.0"
14 | 
15 |     // Valid values: "sync", "async"
16 |     // This controls what `b.FunctionName()` will be (sync or async).
17 |     default_client_mode async
18 | }
19 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/02-calculator-tools/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "name": "my-agent",
 3 |     "version": "0.1.0",
 4 |     "private": true,
 5 |     "scripts": {
 6 |         "dev": "tsx src/index.ts",
 7 |         "build": "tsc"
 8 |     },
 9 |     "dependencies": {
10 |         "baml": "^0.0.0",
11 |         "tsx": "^4.15.0",
12 |         "typescript": "^5.0.0"
13 |     },
14 |     "devDependencies": {
15 |         "@types/node": "^20.0.0",
16 |         "@typescript-eslint/eslint-plugin": "^6.0.0",
17 |         "@typescript-eslint/parser": "^6.0.0",
18 |         "eslint": "^8.0.0"
19 |     }
20 | }
21 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/02-calculator-tools/src/agent.ts:
--------------------------------------------------------------------------------
 1 | import { b } from "../baml_client";
 2 | 
 3 | // tool call or a respond to human tool
 4 | type AgentResponse = Awaited<ReturnType<typeof b.DetermineNextStep>>;
 5 | 
 6 | export interface Event {
 7 |     type: string
 8 |     data: any;
 9 | }
10 | 
11 | export class Thread {
12 |     events: Event[] = [];
13 | 
14 |     constructor(events: Event[]) {
15 |         this.events = events;
16 |     }
17 | 
18 |     serializeForLLM() {
19 |         // can change this to whatever custom serialization you want to do, XML, etc
20 |         // e.g. https://github.com/got-agents/agents/blob/59ebbfa236fc376618f16ee08eb0f3bf7b698892/linear-assistant-ts/src/agent.ts#L66-L105
21 |         return JSON.stringify(this.events);
22 |     }
23 | }
24 | 
25 | // right now this just runs one turn with the LLM, but
26 | // we'll update this function to handle all the agent logic
27 | export async function agentLoop(thread: Thread): Promise<AgentResponse> {
28 |     const nextStep = await b.DetermineNextStep(thread.serializeForLLM());
29 |     return nextStep;
30 | }
31 | 
32 | 
33 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/02-calculator-tools/src/cli.ts:
--------------------------------------------------------------------------------
 1 | // cli.ts lets you invoke the agent loop from the command line
 2 | 
 3 | import { agentLoop, Thread, Event } from "./agent";
 4 | 
 5 | export async function cli() {
 6 |     // Get command line arguments, skipping the first two (node and script name)
 7 |     const args = process.argv.slice(2);
 8 | 
 9 |     if (args.length === 0) {
10 |         console.error("Error: Please provide a message as a command line argument");
11 |         process.exit(1);
12 |     }
13 | 
14 |     // Join all arguments into a single message
15 |     const message = args.join(" ");
16 | 
17 |     // Create a new thread with the user's message as the initial event
18 |     const thread = new Thread([{ type: "user_input", data: message }]);
19 | 
20 |     // Run the agent loop with the thread
21 |     const result = await agentLoop(thread);
22 |     console.log(result);
23 | }
24 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/02-calculator-tools/src/index.ts:
--------------------------------------------------------------------------------
 1 | import { cli } from "./cli"
 2 | 
 3 | async function hello(): Promise<void> {
 4 |     console.log('hello, world!')
 5 | }
 6 | 
 7 | async function main() {
 8 |     await cli()
 9 | }
10 | 
11 | main().catch(console.error)


--------------------------------------------------------------------------------
/workshops/2025-05/sections/02-calculator-tools/tsconfig.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "compilerOptions": {
 3 |       "target": "ES2017",
 4 |       "lib": ["esnext"],
 5 |       "allowJs": true,
 6 |       "skipLibCheck": true,
 7 |       "strict": true,
 8 |       "noEmit": true,
 9 |       "esModuleInterop": true,
10 |       "module": "esnext",
11 |       "moduleResolution": "bundler",
12 |       "resolveJsonModule": true,
13 |       "isolatedModules": true,
14 |       "jsx": "preserve",
15 |       "incremental": true,
16 |       "plugins": [],
17 |       "paths": {
18 |         "@/*": ["./*"]
19 |       }
20 |     },
21 |     "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
22 |     "exclude": ["node_modules", "walkthrough"]
23 |   }
24 |   


--------------------------------------------------------------------------------
/workshops/2025-05/sections/02-calculator-tools/walkthrough/02-agent.baml:
--------------------------------------------------------------------------------
 1 | class DoneForNow {
 2 |   intent "done_for_now"
 3 |   message string 
 4 | }
 5 | 
 6 | function DetermineNextStep(
 7 |     thread: string 
 8 | ) -> CalculatorTools | DoneForNow {
 9 |     client "openai/gpt-4o"
10 | 
11 |     prompt #"
12 |         {{ _.role("system") }}
13 | 
14 |         You are a helpful assistant that can help with tasks.
15 | 
16 |         {{ _.role("user") }}
17 | 
18 |         You are working on the following thread:
19 | 
20 |         {{ thread }}
21 | 
22 |         What should the next step be?
23 | 
24 |         {{ ctx.output_format }}
25 |     "#
26 | }
27 | 
28 | test HelloWorld {
29 |   functions [DetermineNextStep]
30 |   args {
31 |     thread #"
32 |       {
33 |         "type": "user_input",
34 |         "data": "hello!"
35 |       }
36 |     "#
37 |   }
38 | }


--------------------------------------------------------------------------------
/workshops/2025-05/sections/02-calculator-tools/walkthrough/02-tool_calculator.baml:
--------------------------------------------------------------------------------
 1 | type CalculatorTools = AddTool | SubtractTool | MultiplyTool | DivideTool
 2 | 
 3 | 
 4 | class AddTool {
 5 |     intent "add"
 6 |     a int | float
 7 |     b int | float
 8 | }
 9 | 
10 | class SubtractTool {
11 |     intent "subtract"
12 |     a int | float
13 |     b int | float
14 | }
15 | 
16 | class MultiplyTool {
17 |     intent "multiply"
18 |     a int | float
19 |     b int | float
20 | }
21 | 
22 | class DivideTool {
23 |     intent "divide"
24 |     a int | float
25 |     b int | float
26 | }
27 | 
28 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/03-tool-loop/.gitignore:
--------------------------------------------------------------------------------
1 | baml_client/
2 | node_modules/
3 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/03-tool-loop/baml_src/agent.baml:
--------------------------------------------------------------------------------
 1 | class DoneForNow {
 2 |   intent "done_for_now"
 3 |   message string 
 4 | }
 5 | 
 6 | function DetermineNextStep(
 7 |     thread: string 
 8 | ) -> CalculatorTools | DoneForNow {
 9 |     client "openai/gpt-4o"
10 | 
11 |     prompt #"
12 |         {{ _.role("system") }}
13 | 
14 |         You are a helpful assistant that can help with tasks.
15 | 
16 |         {{ _.role("user") }}
17 | 
18 |         You are working on the following thread:
19 | 
20 |         {{ thread }}
21 | 
22 |         What should the next step be?
23 | 
24 |         {{ ctx.output_format }}
25 |     "#
26 | }
27 | 
28 | test HelloWorld {
29 |   functions [DetermineNextStep]
30 |   args {
31 |     thread #"
32 |       {
33 |         "type": "user_input",
34 |         "data": "hello!"
35 |       }
36 |     "#
37 |   }
38 | }


--------------------------------------------------------------------------------
/workshops/2025-05/sections/03-tool-loop/baml_src/clients.baml:
--------------------------------------------------------------------------------
 1 | // Learn more about clients at https://docs.boundaryml.com/docs/snippets/clients/overview
 2 | 
 3 | client<llm> CustomGPT4o {
 4 |   provider openai
 5 |   options {
 6 |     model "gpt-4o"
 7 |     api_key env.OPENAI_API_KEY
 8 |   }
 9 | }
10 | 
11 | client<llm> CustomGPT4oMini {
12 |   provider openai
13 |   retry_policy Exponential
14 |   options {
15 |     model "gpt-4o-mini"
16 |     api_key env.OPENAI_API_KEY
17 |   }
18 | }
19 | 
20 | client<llm> CustomSonnet {
21 |   provider anthropic
22 |   options {
23 |     model "claude-3-5-sonnet-20241022"
24 |     api_key env.ANTHROPIC_API_KEY
25 |   }
26 | }
27 | 
28 | 
29 | client<llm> CustomHaiku {
30 |   provider anthropic
31 |   retry_policy Constant
32 |   options {
33 |     model "claude-3-haiku-20240307"
34 |     api_key env.ANTHROPIC_API_KEY
35 |   }
36 | }
37 | 
38 | // https://docs.boundaryml.com/docs/snippets/clients/round-robin
39 | client<llm> CustomFast {
40 |   provider round-robin
41 |   options {
42 |     // This will alternate between the two clients
43 |     strategy [CustomGPT4oMini, CustomHaiku]
44 |   }
45 | }
46 | 
47 | // https://docs.boundaryml.com/docs/snippets/clients/fallback
48 | client<llm> OpenaiFallback {
49 |   provider fallback
50 |   options {
51 |     // This will try the clients in order until one succeeds
52 |     strategy [CustomGPT4oMini, CustomGPT4oMini]
53 |   }
54 | }
55 | 
56 | // https://docs.boundaryml.com/docs/snippets/clients/retry
57 | retry_policy Constant {
58 |   max_retries 3
59 |   // Strategy is optional
60 |   strategy {
61 |     type constant_delay
62 |     delay_ms 200
63 |   }
64 | }
65 | 
66 | retry_policy Exponential {
67 |   max_retries 2
68 |   // Strategy is optional
69 |   strategy {
70 |     type exponential_backoff
71 |     delay_ms 300
72 |     multiplier 1.5
73 |     max_delay_ms 10000
74 |   }
75 | }


--------------------------------------------------------------------------------
/workshops/2025-05/sections/03-tool-loop/baml_src/generators.baml:
--------------------------------------------------------------------------------
 1 | // This helps use auto generate libraries you can use in the language of
 2 | // your choice. You can have multiple generators if you use multiple languages.
 3 | // Just ensure that the output_dir is different for each generator.
 4 | generator target {
 5 |     // Valid values: "python/pydantic", "typescript", "ruby/sorbet", "rest/openapi"
 6 |     output_type "typescript"
 7 | 
 8 |     // Where the generated code will be saved (relative to baml_src/)
 9 |     output_dir "../"
10 | 
11 |     // The version of the BAML package you have installed (e.g. same version as your baml-py or @boundaryml/baml).
12 |     // The BAML VSCode extension version should also match this version.
13 |     version "0.85.0"
14 | 
15 |     // Valid values: "sync", "async"
16 |     // This controls what `b.FunctionName()` will be (sync or async).
17 |     default_client_mode async
18 | }
19 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/03-tool-loop/baml_src/tool_calculator.baml:
--------------------------------------------------------------------------------
 1 | type CalculatorTools = AddTool | SubtractTool | MultiplyTool | DivideTool
 2 | 
 3 | 
 4 | class AddTool {
 5 |     intent "add"
 6 |     a int | float
 7 |     b int | float
 8 | }
 9 | 
10 | class SubtractTool {
11 |     intent "subtract"
12 |     a int | float
13 |     b int | float
14 | }
15 | 
16 | class MultiplyTool {
17 |     intent "multiply"
18 |     a int | float
19 |     b int | float
20 | }
21 | 
22 | class DivideTool {
23 |     intent "divide"
24 |     a int | float
25 |     b int | float
26 | }
27 | 
28 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/03-tool-loop/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "name": "my-agent",
 3 |     "version": "0.1.0",
 4 |     "private": true,
 5 |     "scripts": {
 6 |         "dev": "tsx src/index.ts",
 7 |         "build": "tsc"
 8 |     },
 9 |     "dependencies": {
10 |         "baml": "^0.0.0",
11 |         "tsx": "^4.15.0",
12 |         "typescript": "^5.0.0"
13 |     },
14 |     "devDependencies": {
15 |         "@types/node": "^20.0.0",
16 |         "@typescript-eslint/eslint-plugin": "^6.0.0",
17 |         "@typescript-eslint/parser": "^6.0.0",
18 |         "eslint": "^8.0.0"
19 |     }
20 | }
21 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/03-tool-loop/src/agent.ts:
--------------------------------------------------------------------------------
 1 | import { b } from "../baml_client";
 2 | 
 3 | // tool call or a respond to human tool
 4 | type AgentResponse = Awaited<ReturnType<typeof b.DetermineNextStep>>;
 5 | 
 6 | export interface Event {
 7 |     type: string
 8 |     data: any;
 9 | }
10 | 
11 | export class Thread {
12 |     events: Event[] = [];
13 | 
14 |     constructor(events: Event[]) {
15 |         this.events = events;
16 |     }
17 | 
18 |     serializeForLLM() {
19 |         // can change this to whatever custom serialization you want to do, XML, etc
20 |         // e.g. https://github.com/got-agents/agents/blob/59ebbfa236fc376618f16ee08eb0f3bf7b698892/linear-assistant-ts/src/agent.ts#L66-L105
21 |         return JSON.stringify(this.events);
22 |     }
23 | }
24 | 
25 | // right now this just runs one turn with the LLM, but
26 | // we'll update this function to handle all the agent logic
27 | export async function agentLoop(thread: Thread): Promise<AgentResponse> {
28 |     const nextStep = await b.DetermineNextStep(thread.serializeForLLM());
29 |     return nextStep;
30 | }
31 | 
32 | 
33 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/03-tool-loop/src/cli.ts:
--------------------------------------------------------------------------------
 1 | // cli.ts lets you invoke the agent loop from the command line
 2 | 
 3 | import { agentLoop, Thread, Event } from "./agent";
 4 | 
 5 | export async function cli() {
 6 |     // Get command line arguments, skipping the first two (node and script name)
 7 |     const args = process.argv.slice(2);
 8 | 
 9 |     if (args.length === 0) {
10 |         console.error("Error: Please provide a message as a command line argument");
11 |         process.exit(1);
12 |     }
13 | 
14 |     // Join all arguments into a single message
15 |     const message = args.join(" ");
16 | 
17 |     // Create a new thread with the user's message as the initial event
18 |     const thread = new Thread([{ type: "user_input", data: message }]);
19 | 
20 |     // Run the agent loop with the thread
21 |     const result = await agentLoop(thread);
22 |     console.log(result);
23 | }
24 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/03-tool-loop/src/index.ts:
--------------------------------------------------------------------------------
 1 | import { cli } from "./cli"
 2 | 
 3 | async function hello(): Promise<void> {
 4 |     console.log('hello, world!')
 5 | }
 6 | 
 7 | async function main() {
 8 |     await cli()
 9 | }
10 | 
11 | main().catch(console.error)


--------------------------------------------------------------------------------
/workshops/2025-05/sections/03-tool-loop/tsconfig.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "compilerOptions": {
 3 |       "target": "ES2017",
 4 |       "lib": ["esnext"],
 5 |       "allowJs": true,
 6 |       "skipLibCheck": true,
 7 |       "strict": true,
 8 |       "noEmit": true,
 9 |       "esModuleInterop": true,
10 |       "module": "esnext",
11 |       "moduleResolution": "bundler",
12 |       "resolveJsonModule": true,
13 |       "isolatedModules": true,
14 |       "jsx": "preserve",
15 |       "incremental": true,
16 |       "plugins": [],
17 |       "paths": {
18 |         "@/*": ["./*"]
19 |       }
20 |     },
21 |     "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
22 |     "exclude": ["node_modules", "walkthrough"]
23 |   }
24 |   


--------------------------------------------------------------------------------
/workshops/2025-05/sections/03-tool-loop/walkthrough/03-agent.ts:
--------------------------------------------------------------------------------
 1 | import { b } from "../baml_client";
 2 | 
 3 | // tool call or a respond to human tool
 4 | type AgentResponse = Awaited<ReturnType<typeof b.DetermineNextStep>>;
 5 | 
 6 | export interface Event {
 7 |     type: string
 8 |     data: any;
 9 | }
10 | 
11 | export class Thread {
12 |     events: Event[] = [];
13 | 
14 |     constructor(events: Event[]) {
15 |         this.events = events;
16 |     }
17 | 
18 |     serializeForLLM() {
19 |         // can change this to whatever custom serialization you want to do, XML, etc
20 |         // e.g. https://github.com/got-agents/agents/blob/59ebbfa236fc376618f16ee08eb0f3bf7b698892/linear-assistant-ts/src/agent.ts#L66-L105
21 |         return JSON.stringify(this.events);
22 |     }
23 | }
24 | 
25 | 
26 | 
27 | export async function agentLoop(thread: Thread): Promise<string> {
28 | 
29 |     while (true) {
30 |         const nextStep = await b.DetermineNextStep(thread.serializeForLLM());
31 |         console.log("nextStep", nextStep);
32 | 
33 |         switch (nextStep.intent) {
34 |             case "done_for_now":
35 |                 // response to human, return the next step object
36 |                 return nextStep.message;
37 |             case "add":
38 |                 thread.events.push({
39 |                     "type": "tool_call",
40 |                     "data": nextStep
41 |                 });
42 |                 const result = nextStep.a + nextStep.b;
43 |                 console.log("tool_response", result);
44 |                 thread.events.push({
45 |                     "type": "tool_response",
46 |                     "data": result
47 |                 });
48 |                 continue;
49 |             default:
50 |                 throw new Error(`Unknown intent: ${nextStep.intent}`);
51 |         }
52 |     }
53 | }
54 | 
55 | 
56 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/04-baml-tests/.gitignore:
--------------------------------------------------------------------------------
1 | baml_client/
2 | node_modules/
3 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/04-baml-tests/baml_src/agent.baml:
--------------------------------------------------------------------------------
 1 | class DoneForNow {
 2 |   intent "done_for_now"
 3 |   message string 
 4 | }
 5 | 
 6 | function DetermineNextStep(
 7 |     thread: string 
 8 | ) -> CalculatorTools | DoneForNow {
 9 |     client "openai/gpt-4o"
10 | 
11 |     prompt #"
12 |         {{ _.role("system") }}
13 | 
14 |         You are a helpful assistant that can help with tasks.
15 | 
16 |         {{ _.role("user") }}
17 | 
18 |         You are working on the following thread:
19 | 
20 |         {{ thread }}
21 | 
22 |         What should the next step be?
23 | 
24 |         {{ ctx.output_format }}
25 |     "#
26 | }
27 | 
28 | test HelloWorld {
29 |   functions [DetermineNextStep]
30 |   args {
31 |     thread #"
32 |       {
33 |         "type": "user_input",
34 |         "data": "hello!"
35 |       }
36 |     "#
37 |   }
38 | }


--------------------------------------------------------------------------------
/workshops/2025-05/sections/04-baml-tests/baml_src/clients.baml:
--------------------------------------------------------------------------------
 1 | // Learn more about clients at https://docs.boundaryml.com/docs/snippets/clients/overview
 2 | 
 3 | client<llm> CustomGPT4o {
 4 |   provider openai
 5 |   options {
 6 |     model "gpt-4o"
 7 |     api_key env.OPENAI_API_KEY
 8 |   }
 9 | }
10 | 
11 | client<llm> CustomGPT4oMini {
12 |   provider openai
13 |   retry_policy Exponential
14 |   options {
15 |     model "gpt-4o-mini"
16 |     api_key env.OPENAI_API_KEY
17 |   }
18 | }
19 | 
20 | client<llm> CustomSonnet {
21 |   provider anthropic
22 |   options {
23 |     model "claude-3-5-sonnet-20241022"
24 |     api_key env.ANTHROPIC_API_KEY
25 |   }
26 | }
27 | 
28 | 
29 | client<llm> CustomHaiku {
30 |   provider anthropic
31 |   retry_policy Constant
32 |   options {
33 |     model "claude-3-haiku-20240307"
34 |     api_key env.ANTHROPIC_API_KEY
35 |   }
36 | }
37 | 
38 | // https://docs.boundaryml.com/docs/snippets/clients/round-robin
39 | client<llm> CustomFast {
40 |   provider round-robin
41 |   options {
42 |     // This will alternate between the two clients
43 |     strategy [CustomGPT4oMini, CustomHaiku]
44 |   }
45 | }
46 | 
47 | // https://docs.boundaryml.com/docs/snippets/clients/fallback
48 | client<llm> OpenaiFallback {
49 |   provider fallback
50 |   options {
51 |     // This will try the clients in order until one succeeds
52 |     strategy [CustomGPT4oMini, CustomGPT4oMini]
53 |   }
54 | }
55 | 
56 | // https://docs.boundaryml.com/docs/snippets/clients/retry
57 | retry_policy Constant {
58 |   max_retries 3
59 |   // Strategy is optional
60 |   strategy {
61 |     type constant_delay
62 |     delay_ms 200
63 |   }
64 | }
65 | 
66 | retry_policy Exponential {
67 |   max_retries 2
68 |   // Strategy is optional
69 |   strategy {
70 |     type exponential_backoff
71 |     delay_ms 300
72 |     multiplier 1.5
73 |     max_delay_ms 10000
74 |   }
75 | }


--------------------------------------------------------------------------------
/workshops/2025-05/sections/04-baml-tests/baml_src/generators.baml:
--------------------------------------------------------------------------------
 1 | // This helps use auto generate libraries you can use in the language of
 2 | // your choice. You can have multiple generators if you use multiple languages.
 3 | // Just ensure that the output_dir is different for each generator.
 4 | generator target {
 5 |     // Valid values: "python/pydantic", "typescript", "ruby/sorbet", "rest/openapi"
 6 |     output_type "typescript"
 7 | 
 8 |     // Where the generated code will be saved (relative to baml_src/)
 9 |     output_dir "../"
10 | 
11 |     // The version of the BAML package you have installed (e.g. same version as your baml-py or @boundaryml/baml).
12 |     // The BAML VSCode extension version should also match this version.
13 |     version "0.85.0"
14 | 
15 |     // Valid values: "sync", "async"
16 |     // This controls what `b.FunctionName()` will be (sync or async).
17 |     default_client_mode async
18 | }
19 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/04-baml-tests/baml_src/tool_calculator.baml:
--------------------------------------------------------------------------------
 1 | type CalculatorTools = AddTool | SubtractTool | MultiplyTool | DivideTool
 2 | 
 3 | 
 4 | class AddTool {
 5 |     intent "add"
 6 |     a int | float
 7 |     b int | float
 8 | }
 9 | 
10 | class SubtractTool {
11 |     intent "subtract"
12 |     a int | float
13 |     b int | float
14 | }
15 | 
16 | class MultiplyTool {
17 |     intent "multiply"
18 |     a int | float
19 |     b int | float
20 | }
21 | 
22 | class DivideTool {
23 |     intent "divide"
24 |     a int | float
25 |     b int | float
26 | }
27 | 
28 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/04-baml-tests/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "name": "my-agent",
 3 |     "version": "0.1.0",
 4 |     "private": true,
 5 |     "scripts": {
 6 |         "dev": "tsx src/index.ts",
 7 |         "build": "tsc"
 8 |     },
 9 |     "dependencies": {
10 |         "baml": "^0.0.0",
11 |         "tsx": "^4.15.0",
12 |         "typescript": "^5.0.0"
13 |     },
14 |     "devDependencies": {
15 |         "@types/node": "^20.0.0",
16 |         "@typescript-eslint/eslint-plugin": "^6.0.0",
17 |         "@typescript-eslint/parser": "^6.0.0",
18 |         "eslint": "^8.0.0"
19 |     }
20 | }
21 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/04-baml-tests/src/cli.ts:
--------------------------------------------------------------------------------
 1 | // cli.ts lets you invoke the agent loop from the command line
 2 | 
 3 | import { agentLoop, Thread, Event } from "./agent";
 4 | 
 5 | export async function cli() {
 6 |     // Get command line arguments, skipping the first two (node and script name)
 7 |     const args = process.argv.slice(2);
 8 | 
 9 |     if (args.length === 0) {
10 |         console.error("Error: Please provide a message as a command line argument");
11 |         process.exit(1);
12 |     }
13 | 
14 |     // Join all arguments into a single message
15 |     const message = args.join(" ");
16 | 
17 |     // Create a new thread with the user's message as the initial event
18 |     const thread = new Thread([{ type: "user_input", data: message }]);
19 | 
20 |     // Run the agent loop with the thread
21 |     const result = await agentLoop(thread);
22 |     console.log(result);
23 | }
24 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/04-baml-tests/src/index.ts:
--------------------------------------------------------------------------------
 1 | import { cli } from "./cli"
 2 | 
 3 | async function hello(): Promise<void> {
 4 |     console.log('hello, world!')
 5 | }
 6 | 
 7 | async function main() {
 8 |     await cli()
 9 | }
10 | 
11 | main().catch(console.error)


--------------------------------------------------------------------------------
/workshops/2025-05/sections/04-baml-tests/tsconfig.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "compilerOptions": {
 3 |       "target": "ES2017",
 4 |       "lib": ["esnext"],
 5 |       "allowJs": true,
 6 |       "skipLibCheck": true,
 7 |       "strict": true,
 8 |       "noEmit": true,
 9 |       "esModuleInterop": true,
10 |       "module": "esnext",
11 |       "moduleResolution": "bundler",
12 |       "resolveJsonModule": true,
13 |       "isolatedModules": true,
14 |       "jsx": "preserve",
15 |       "incremental": true,
16 |       "plugins": [],
17 |       "paths": {
18 |         "@/*": ["./*"]
19 |       }
20 |     },
21 |     "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
22 |     "exclude": ["node_modules", "walkthrough"]
23 |   }
24 |   


--------------------------------------------------------------------------------
/workshops/2025-05/sections/04-baml-tests/walkthrough/04-agent.baml:
--------------------------------------------------------------------------------
 1 | class DoneForNow {
 2 |   intent "done_for_now"
 3 |   message string 
 4 | }
 5 | 
 6 | function DetermineNextStep(
 7 |     thread: string 
 8 | ) -> CalculatorTools | DoneForNow {
 9 |     client "openai/gpt-4o"
10 | 
11 |     prompt #"
12 |         {{ _.role("system") }}
13 | 
14 |         You are a helpful assistant that can help with tasks.
15 | 
16 |         {{ _.role("user") }}
17 | 
18 |         You are working on the following thread:
19 | 
20 |         {{ thread }}
21 | 
22 |         What should the next step be?
23 | 
24 |         {{ ctx.output_format }}
25 |     "#
26 | }
27 | 
28 | test HelloWorld {
29 |   functions [DetermineNextStep]
30 |   args {
31 |     thread #"
32 |       {
33 |         "type": "user_input",
34 |         "data": "hello!"
35 |       }
36 |     "#
37 |   }
38 | }
39 | 
40 | test MathOperation {
41 |   functions [DetermineNextStep]
42 |   args {
43 |     thread #"
44 |       {
45 |         "type": "user_input",
46 |         "data": "can you multiply 3 and 4?"
47 |       }
48 |     "#
49 |   }
50 | }
51 | 
52 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/04-baml-tests/walkthrough/04b-agent.baml:
--------------------------------------------------------------------------------
 1 | class DoneForNow {
 2 |   intent "done_for_now"
 3 |   message string 
 4 | }
 5 | 
 6 | function DetermineNextStep(
 7 |     thread: string 
 8 | ) -> CalculatorTools | DoneForNow {
 9 |     client "openai/gpt-4o"
10 | 
11 |     prompt #"
12 |         {{ _.role("system") }}
13 | 
14 |         You are a helpful assistant that can help with tasks.
15 | 
16 |         {{ _.role("user") }}
17 | 
18 |         You are working on the following thread:
19 | 
20 |         {{ thread }}
21 | 
22 |         What should the next step be?
23 | 
24 |         {{ ctx.output_format }}
25 |     "#
26 | }
27 | 
28 | test HelloWorld {
29 |   functions [DetermineNextStep]
30 |   args {
31 |     thread #"
32 |       {
33 |         "type": "user_input",
34 |         "data": "hello!"
35 |       }
36 |     "#
37 |   }
38 |   @@assert(hello, {{this.intent == "done_for_now"}})
39 | }
40 | 
41 | test MathOperation {
42 |   functions [DetermineNextStep]
43 |   args {
44 |     thread #"
45 |       {
46 |         "type": "user_input",
47 |         "data": "can you multiply 3 and 4?"
48 |       }
49 |     "#
50 |   }
51 |   @@assert(math_operation, {{this.intent == "multiply"}})
52 | }
53 | 
54 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/05-human-tools/.gitignore:
--------------------------------------------------------------------------------
1 | baml_client/
2 | node_modules/
3 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/05-human-tools/baml_src/clients.baml:
--------------------------------------------------------------------------------
 1 | // Learn more about clients at https://docs.boundaryml.com/docs/snippets/clients/overview
 2 | 
 3 | client<llm> CustomGPT4o {
 4 |   provider openai
 5 |   options {
 6 |     model "gpt-4o"
 7 |     api_key env.OPENAI_API_KEY
 8 |   }
 9 | }
10 | 
11 | client<llm> CustomGPT4oMini {
12 |   provider openai
13 |   retry_policy Exponential
14 |   options {
15 |     model "gpt-4o-mini"
16 |     api_key env.OPENAI_API_KEY
17 |   }
18 | }
19 | 
20 | client<llm> CustomSonnet {
21 |   provider anthropic
22 |   options {
23 |     model "claude-3-5-sonnet-20241022"
24 |     api_key env.ANTHROPIC_API_KEY
25 |   }
26 | }
27 | 
28 | 
29 | client<llm> CustomHaiku {
30 |   provider anthropic
31 |   retry_policy Constant
32 |   options {
33 |     model "claude-3-haiku-20240307"
34 |     api_key env.ANTHROPIC_API_KEY
35 |   }
36 | }
37 | 
38 | // https://docs.boundaryml.com/docs/snippets/clients/round-robin
39 | client<llm> CustomFast {
40 |   provider round-robin
41 |   options {
42 |     // This will alternate between the two clients
43 |     strategy [CustomGPT4oMini, CustomHaiku]
44 |   }
45 | }
46 | 
47 | // https://docs.boundaryml.com/docs/snippets/clients/fallback
48 | client<llm> OpenaiFallback {
49 |   provider fallback
50 |   options {
51 |     // This will try the clients in order until one succeeds
52 |     strategy [CustomGPT4oMini, CustomGPT4oMini]
53 |   }
54 | }
55 | 
56 | // https://docs.boundaryml.com/docs/snippets/clients/retry
57 | retry_policy Constant {
58 |   max_retries 3
59 |   // Strategy is optional
60 |   strategy {
61 |     type constant_delay
62 |     delay_ms 200
63 |   }
64 | }
65 | 
66 | retry_policy Exponential {
67 |   max_retries 2
68 |   // Strategy is optional
69 |   strategy {
70 |     type exponential_backoff
71 |     delay_ms 300
72 |     multiplier 1.5
73 |     max_delay_ms 10000
74 |   }
75 | }


--------------------------------------------------------------------------------
/workshops/2025-05/sections/05-human-tools/baml_src/generators.baml:
--------------------------------------------------------------------------------
 1 | // This helps use auto generate libraries you can use in the language of
 2 | // your choice. You can have multiple generators if you use multiple languages.
 3 | // Just ensure that the output_dir is different for each generator.
 4 | generator target {
 5 |     // Valid values: "python/pydantic", "typescript", "ruby/sorbet", "rest/openapi"
 6 |     output_type "typescript"
 7 | 
 8 |     // Where the generated code will be saved (relative to baml_src/)
 9 |     output_dir "../"
10 | 
11 |     // The version of the BAML package you have installed (e.g. same version as your baml-py or @boundaryml/baml).
12 |     // The BAML VSCode extension version should also match this version.
13 |     version "0.202.0"
14 | 
15 |     // Valid values: "sync", "async"
16 |     // This controls what `b.FunctionName()` will be (sync or async).
17 |     default_client_mode async
18 | }
19 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/05-human-tools/baml_src/tool_calculator.baml:
--------------------------------------------------------------------------------
 1 | type CalculatorTools = AddTool | SubtractTool | MultiplyTool | DivideTool
 2 | 
 3 | 
 4 | class AddTool {
 5 |     intent "add"
 6 |     a int | float
 7 |     b int | float
 8 | }
 9 | 
10 | class SubtractTool {
11 |     intent "subtract"
12 |     a int | float
13 |     b int | float
14 | }
15 | 
16 | class MultiplyTool {
17 |     intent "multiply"
18 |     a int | float
19 |     b int | float
20 | }
21 | 
22 | class DivideTool {
23 |     intent "divide"
24 |     a int | float
25 |     b int | float
26 | }
27 | 
28 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/05-human-tools/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "name": "my-agent",
 3 |     "version": "0.1.0",
 4 |     "private": true,
 5 |     "scripts": {
 6 |         "dev": "tsx src/index.ts",
 7 |         "build": "tsc"
 8 |     },
 9 |     "dependencies": {
10 |         "@boundaryml/baml": "latest",
11 |         "tsx": "^4.15.0",
12 |         "typescript": "^5.0.0"
13 |     },
14 |     "devDependencies": {
15 |         "@types/node": "^20.0.0",
16 |         "@typescript-eslint/eslint-plugin": "^6.0.0",
17 |         "@typescript-eslint/parser": "^6.0.0",
18 |         "eslint": "^8.0.0"
19 |     }
20 | }
21 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/05-human-tools/src/cli.ts:
--------------------------------------------------------------------------------
 1 | // cli.ts lets you invoke the agent loop from the command line
 2 | 
 3 | import { agentLoop, Thread, Event } from "./agent";
 4 | 
 5 | export async function cli() {
 6 |     // Get command line arguments, skipping the first two (node and script name)
 7 |     const args = process.argv.slice(2);
 8 | 
 9 |     if (args.length === 0) {
10 |         console.error("Error: Please provide a message as a command line argument");
11 |         process.exit(1);
12 |     }
13 | 
14 |     // Join all arguments into a single message
15 |     const message = args.join(" ");
16 | 
17 |     // Create a new thread with the user's message as the initial event
18 |     const thread = new Thread([{ type: "user_input", data: message }]);
19 | 
20 |     // Run the agent loop with the thread
21 |     const result = await agentLoop(thread);
22 |     console.log(result);
23 | }
24 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/05-human-tools/src/index.ts:
--------------------------------------------------------------------------------
 1 | import { cli } from "./cli"
 2 | 
 3 | async function hello(): Promise<void> {
 4 |     console.log('hello, world!')
 5 | }
 6 | 
 7 | async function main() {
 8 |     await cli()
 9 | }
10 | 
11 | main().catch(console.error)


--------------------------------------------------------------------------------
/workshops/2025-05/sections/05-human-tools/tsconfig.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "compilerOptions": {
 3 |       "target": "ES2017",
 4 |       "lib": ["esnext"],
 5 |       "allowJs": true,
 6 |       "skipLibCheck": true,
 7 |       "strict": true,
 8 |       "noEmit": true,
 9 |       "esModuleInterop": true,
10 |       "module": "esnext",
11 |       "moduleResolution": "bundler",
12 |       "resolveJsonModule": true,
13 |       "isolatedModules": true,
14 |       "jsx": "preserve",
15 |       "incremental": true,
16 |       "plugins": [],
17 |       "paths": {
18 |         "@/*": ["./*"]
19 |       }
20 |     },
21 |     "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
22 |     "exclude": ["node_modules", "walkthrough"]
23 |   }
24 |   


--------------------------------------------------------------------------------
/workshops/2025-05/sections/05-human-tools/walkthrough/05-cli.ts:
--------------------------------------------------------------------------------
 1 | // cli.ts lets you invoke the agent loop from the command line
 2 | 
 3 | import { agentLoop, Thread, Event } from "../src/agent";
 4 | 
 5 | 
 6 | 
 7 | export async function cli() {
 8 |     // Get command line arguments, skipping the first two (node and script name)
 9 |     const args = process.argv.slice(2);
10 | 
11 |     if (args.length === 0) {
12 |         console.error("Error: Please provide a message as a command line argument");
13 |         process.exit(1);
14 |     }
15 | 
16 |     // Join all arguments into a single message
17 |     const message = args.join(" ");
18 | 
19 |     // Create a new thread with the user's message as the initial event
20 |     const thread = new Thread([{ type: "user_input", data: message }]);
21 | 
22 |     // Run the agent loop with the thread
23 |     const result = await agentLoop(thread);
24 |     let lastEvent = result.events.slice(-1)[0];
25 | 
26 |     while (lastEvent.data.intent === "request_more_information") {
27 |         const message = await askHuman(lastEvent.data.message);
28 |         thread.events.push({ type: "human_response", data: message });
29 |         const result = await agentLoop(thread);
30 |         lastEvent = result.events.slice(-1)[0];
31 |     }
32 | 
33 |     // print the final result
34 |     // optional - you could loop here too
35 |     console.log(lastEvent.data.message);
36 |     process.exit(0);
37 | }
38 | 
39 | async function askHuman(message: string) {
40 |     const readline = require('readline').createInterface({
41 |         input: process.stdin,
42 |         output: process.stdout
43 |     });
44 | 
45 |     return new Promise((resolve) => {
46 |         readline.question(`${message}\n> `, (answer: string) => {
47 |             resolve(answer);
48 |         });
49 |     });
50 | }
51 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/06-customize-prompt/.gitignore:
--------------------------------------------------------------------------------
1 | baml_client/
2 | node_modules/
3 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/06-customize-prompt/README.md:
--------------------------------------------------------------------------------
 1 | # Chapter 6 - Customize Your Prompt with Reasoning
 2 | 
 3 | In this section, we'll explore how to customize the prompt of the agent
 4 | with reasoning steps.
 5 | 
 6 | this is core to [factor 2 - own your prompts](https://github.com/humanlayer/12-factor-agents/blob/main/content/factor-2-own-your-prompts.md)
 7 | 
 8 | there's a deep dive on reasoning on AI That Works [reasoning models versus reasoning steps](https://github.com/hellovai/ai-that-works/tree/main/2025-04-07-reasoning-models-vs-prompts)
 9 | 
10 | 
11 | for this section, it will be helpful to leave the baml logs enabled
12 | 
13 |     export BAML_LOG=debug
14 | 
15 | update the agent prompt to include a reasoning step
16 | 
17 | 
18 | ```diff
19 | baml_src/agent.baml
20 |  
21 |          {{ ctx.output_format }}
22 | +
23 | +        First, always plan out what to do next, for example:
24 | +
25 | +        - ...
26 | +        - ...
27 | +        - ...
28 | +
29 | +        {...} // schema
30 |      "#
31 |  }
32 |    @@assert(b, {{this.a == 3}})
33 |  }
34 | -        
35 | -
36 | ```
37 | 
38 | <details>
39 | <summary>skip this step</summary>
40 | 
41 |     cp ./walkthrough/06-agent.baml baml_src/agent.baml
42 | 
43 | </details>
44 | 
45 | generate the updated client
46 | 
47 |     npx baml-cli generate
48 | 
49 | now, you can try it out with a simple prompt
50 | 
51 | 
52 |     npx tsx src/index.ts 'can you multiply 3 and 4'
53 | 
54 | you should see output from the baml logs showing the reasoning steps
55 | 
56 | #### optional challenge 
57 | 
58 | add a field to your tool output format that includes the reasoning steps in the output!
59 | 
60 | 
61 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/06-customize-prompt/baml_src/generators.baml:
--------------------------------------------------------------------------------
 1 | // This helps use auto generate libraries you can use in the language of
 2 | // your choice. You can have multiple generators if you use multiple languages.
 3 | // Just ensure that the output_dir is different for each generator.
 4 | generator target {
 5 |     // Valid values: "python/pydantic", "typescript", "ruby/sorbet", "rest/openapi"
 6 |     output_type "typescript"
 7 | 
 8 |     // Where the generated code will be saved (relative to baml_src/)
 9 |     output_dir "../"
10 | 
11 |     // The version of the BAML package you have installed (e.g. same version as your baml-py or @boundaryml/baml).
12 |     // The BAML VSCode extension version should also match this version.
13 |     version "0.85.0"
14 | 
15 |     // Valid values: "sync", "async"
16 |     // This controls what `b.FunctionName()` will be (sync or async).
17 |     default_client_mode async
18 | }
19 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/06-customize-prompt/baml_src/tool_calculator.baml:
--------------------------------------------------------------------------------
 1 | type CalculatorTools = AddTool | SubtractTool | MultiplyTool | DivideTool
 2 | 
 3 | 
 4 | class AddTool {
 5 |     intent "add"
 6 |     a int | float
 7 |     b int | float
 8 | }
 9 | 
10 | class SubtractTool {
11 |     intent "subtract"
12 |     a int | float
13 |     b int | float
14 | }
15 | 
16 | class MultiplyTool {
17 |     intent "multiply"
18 |     a int | float
19 |     b int | float
20 | }
21 | 
22 | class DivideTool {
23 |     intent "divide"
24 |     a int | float
25 |     b int | float
26 | }
27 | 
28 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/06-customize-prompt/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "name": "my-agent",
 3 |     "version": "0.1.0",
 4 |     "private": true,
 5 |     "scripts": {
 6 |         "dev": "tsx src/index.ts",
 7 |         "build": "tsc"
 8 |     },
 9 |     "dependencies": {
10 |         "baml": "^0.0.0",
11 |         "tsx": "^4.15.0",
12 |         "typescript": "^5.0.0"
13 |     },
14 |     "devDependencies": {
15 |         "@types/node": "^20.0.0",
16 |         "@typescript-eslint/eslint-plugin": "^6.0.0",
17 |         "@typescript-eslint/parser": "^6.0.0",
18 |         "eslint": "^8.0.0"
19 |     }
20 | }
21 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/06-customize-prompt/src/cli.ts:
--------------------------------------------------------------------------------
 1 | // cli.ts lets you invoke the agent loop from the command line
 2 | 
 3 | import { agentLoop, Thread, Event } from "../src/agent";
 4 | 
 5 | 
 6 | 
 7 | export async function cli() {
 8 |     // Get command line arguments, skipping the first two (node and script name)
 9 |     const args = process.argv.slice(2);
10 | 
11 |     if (args.length === 0) {
12 |         console.error("Error: Please provide a message as a command line argument");
13 |         process.exit(1);
14 |     }
15 | 
16 |     // Join all arguments into a single message
17 |     const message = args.join(" ");
18 | 
19 |     // Create a new thread with the user's message as the initial event
20 |     const thread = new Thread([{ type: "user_input", data: message }]);
21 | 
22 |     // Run the agent loop with the thread
23 |     const result = await agentLoop(thread);
24 |     let lastEvent = result.events.slice(-1)[0];
25 | 
26 |     while (lastEvent.data.intent === "request_more_information") {
27 |         const message = await askHuman(lastEvent.data.message);
28 |         thread.events.push({ type: "human_response", data: message });
29 |         const result = await agentLoop(thread);
30 |         lastEvent = result.events.slice(-1)[0];
31 |     }
32 | 
33 |     // print the final result
34 |     // optional - you could loop here too
35 |     console.log(lastEvent.data.message);
36 |     process.exit(0);
37 | }
38 | 
39 | async function askHuman(message: string) {
40 |     const readline = require('readline').createInterface({
41 |         input: process.stdin,
42 |         output: process.stdout
43 |     });
44 | 
45 |     return new Promise((resolve) => {
46 |         readline.question(`${message}\n> `, (answer: string) => {
47 |             resolve(answer);
48 |         });
49 |     });
50 | }
51 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/06-customize-prompt/src/index.ts:
--------------------------------------------------------------------------------
 1 | import { cli } from "./cli"
 2 | 
 3 | async function hello(): Promise<void> {
 4 |     console.log('hello, world!')
 5 | }
 6 | 
 7 | async function main() {
 8 |     await cli()
 9 | }
10 | 
11 | main().catch(console.error)


--------------------------------------------------------------------------------
/workshops/2025-05/sections/06-customize-prompt/tsconfig.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "compilerOptions": {
 3 |       "target": "ES2017",
 4 |       "lib": ["esnext"],
 5 |       "allowJs": true,
 6 |       "skipLibCheck": true,
 7 |       "strict": true,
 8 |       "noEmit": true,
 9 |       "esModuleInterop": true,
10 |       "module": "esnext",
11 |       "moduleResolution": "bundler",
12 |       "resolveJsonModule": true,
13 |       "isolatedModules": true,
14 |       "jsx": "preserve",
15 |       "incremental": true,
16 |       "plugins": [],
17 |       "paths": {
18 |         "@/*": ["./*"]
19 |       }
20 |     },
21 |     "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
22 |     "exclude": ["node_modules", "walkthrough"]
23 |   }
24 |   


--------------------------------------------------------------------------------
/workshops/2025-05/sections/07-context-window/.gitignore:
--------------------------------------------------------------------------------
1 | baml_client/
2 | node_modules/
3 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/07-context-window/baml_src/clients.baml:
--------------------------------------------------------------------------------
 1 | // Learn more about clients at https://docs.boundaryml.com/docs/snippets/clients/overview
 2 | 
 3 | client<llm> CustomGPT4o {
 4 |   provider openai
 5 |   options {
 6 |     model "gpt-4o"
 7 |     api_key env.OPENAI_API_KEY
 8 |   }
 9 | }
10 | 
11 | client<llm> CustomGPT4oMini {
12 |   provider openai
13 |   retry_policy Exponential
14 |   options {
15 |     model "gpt-4o-mini"
16 |     api_key env.OPENAI_API_KEY
17 |   }
18 | }
19 | 
20 | client<llm> CustomSonnet {
21 |   provider anthropic
22 |   options {
23 |     model "claude-3-5-sonnet-20241022"
24 |     api_key env.ANTHROPIC_API_KEY
25 |   }
26 | }
27 | 
28 | 
29 | client<llm> CustomHaiku {
30 |   provider anthropic
31 |   retry_policy Constant
32 |   options {
33 |     model "claude-3-haiku-20240307"
34 |     api_key env.ANTHROPIC_API_KEY
35 |   }
36 | }
37 | 
38 | // https://docs.boundaryml.com/docs/snippets/clients/round-robin
39 | client<llm> CustomFast {
40 |   provider round-robin
41 |   options {
42 |     // This will alternate between the two clients
43 |     strategy [CustomGPT4oMini, CustomHaiku]
44 |   }
45 | }
46 | 
47 | // https://docs.boundaryml.com/docs/snippets/clients/fallback
48 | client<llm> OpenaiFallback {
49 |   provider fallback
50 |   options {
51 |     // This will try the clients in order until one succeeds
52 |     strategy [CustomGPT4oMini, CustomGPT4oMini]
53 |   }
54 | }
55 | 
56 | // https://docs.boundaryml.com/docs/snippets/clients/retry
57 | retry_policy Constant {
58 |   max_retries 3
59 |   // Strategy is optional
60 |   strategy {
61 |     type constant_delay
62 |     delay_ms 200
63 |   }
64 | }
65 | 
66 | retry_policy Exponential {
67 |   max_retries 2
68 |   // Strategy is optional
69 |   strategy {
70 |     type exponential_backoff
71 |     delay_ms 300
72 |     multiplier 1.5
73 |     max_delay_ms 10000
74 |   }
75 | }


--------------------------------------------------------------------------------
/workshops/2025-05/sections/07-context-window/baml_src/generators.baml:
--------------------------------------------------------------------------------
 1 | // This helps use auto generate libraries you can use in the language of
 2 | // your choice. You can have multiple generators if you use multiple languages.
 3 | // Just ensure that the output_dir is different for each generator.
 4 | generator target {
 5 |     // Valid values: "python/pydantic", "typescript", "ruby/sorbet", "rest/openapi"
 6 |     output_type "typescript"
 7 | 
 8 |     // Where the generated code will be saved (relative to baml_src/)
 9 |     output_dir "../"
10 | 
11 |     // The version of the BAML package you have installed (e.g. same version as your baml-py or @boundaryml/baml).
12 |     // The BAML VSCode extension version should also match this version.
13 |     version "0.85.0"
14 | 
15 |     // Valid values: "sync", "async"
16 |     // This controls what `b.FunctionName()` will be (sync or async).
17 |     default_client_mode async
18 | }
19 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/07-context-window/baml_src/tool_calculator.baml:
--------------------------------------------------------------------------------
 1 | type CalculatorTools = AddTool | SubtractTool | MultiplyTool | DivideTool
 2 | 
 3 | 
 4 | class AddTool {
 5 |     intent "add"
 6 |     a int | float
 7 |     b int | float
 8 | }
 9 | 
10 | class SubtractTool {
11 |     intent "subtract"
12 |     a int | float
13 |     b int | float
14 | }
15 | 
16 | class MultiplyTool {
17 |     intent "multiply"
18 |     a int | float
19 |     b int | float
20 | }
21 | 
22 | class DivideTool {
23 |     intent "divide"
24 |     a int | float
25 |     b int | float
26 | }
27 | 
28 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/07-context-window/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "name": "my-agent",
 3 |     "version": "0.1.0",
 4 |     "private": true,
 5 |     "scripts": {
 6 |         "dev": "tsx src/index.ts",
 7 |         "build": "tsc"
 8 |     },
 9 |     "dependencies": {
10 |         "baml": "^0.0.0",
11 |         "tsx": "^4.15.0",
12 |         "typescript": "^5.0.0"
13 |     },
14 |     "devDependencies": {
15 |         "@types/node": "^20.0.0",
16 |         "@typescript-eslint/eslint-plugin": "^6.0.0",
17 |         "@typescript-eslint/parser": "^6.0.0",
18 |         "eslint": "^8.0.0"
19 |     }
20 | }
21 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/07-context-window/src/cli.ts:
--------------------------------------------------------------------------------
 1 | // cli.ts lets you invoke the agent loop from the command line
 2 | 
 3 | import { agentLoop, Thread, Event } from "../src/agent";
 4 | 
 5 | 
 6 | 
 7 | export async function cli() {
 8 |     // Get command line arguments, skipping the first two (node and script name)
 9 |     const args = process.argv.slice(2);
10 | 
11 |     if (args.length === 0) {
12 |         console.error("Error: Please provide a message as a command line argument");
13 |         process.exit(1);
14 |     }
15 | 
16 |     // Join all arguments into a single message
17 |     const message = args.join(" ");
18 | 
19 |     // Create a new thread with the user's message as the initial event
20 |     const thread = new Thread([{ type: "user_input", data: message }]);
21 | 
22 |     // Run the agent loop with the thread
23 |     const result = await agentLoop(thread);
24 |     let lastEvent = result.events.slice(-1)[0];
25 | 
26 |     while (lastEvent.data.intent === "request_more_information") {
27 |         const message = await askHuman(lastEvent.data.message);
28 |         thread.events.push({ type: "human_response", data: message });
29 |         const result = await agentLoop(thread);
30 |         lastEvent = result.events.slice(-1)[0];
31 |     }
32 | 
33 |     // print the final result
34 |     // optional - you could loop here too
35 |     console.log(lastEvent.data.message);
36 |     process.exit(0);
37 | }
38 | 
39 | async function askHuman(message: string) {
40 |     const readline = require('readline').createInterface({
41 |         input: process.stdin,
42 |         output: process.stdout
43 |     });
44 | 
45 |     return new Promise((resolve) => {
46 |         readline.question(`${message}\n> `, (answer: string) => {
47 |             resolve(answer);
48 |         });
49 |     });
50 | }
51 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/07-context-window/src/index.ts:
--------------------------------------------------------------------------------
 1 | import { cli } from "./cli"
 2 | 
 3 | async function hello(): Promise<void> {
 4 |     console.log('hello, world!')
 5 | }
 6 | 
 7 | async function main() {
 8 |     await cli()
 9 | }
10 | 
11 | main().catch(console.error)


--------------------------------------------------------------------------------
/workshops/2025-05/sections/07-context-window/tsconfig.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "compilerOptions": {
 3 |       "target": "ES2017",
 4 |       "lib": ["esnext"],
 5 |       "allowJs": true,
 6 |       "skipLibCheck": true,
 7 |       "strict": true,
 8 |       "noEmit": true,
 9 |       "esModuleInterop": true,
10 |       "module": "esnext",
11 |       "moduleResolution": "bundler",
12 |       "resolveJsonModule": true,
13 |       "isolatedModules": true,
14 |       "jsx": "preserve",
15 |       "incremental": true,
16 |       "plugins": [],
17 |       "paths": {
18 |         "@/*": ["./*"]
19 |       }
20 |     },
21 |     "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
22 |     "exclude": ["node_modules", "walkthrough"]
23 |   }
24 |   


--------------------------------------------------------------------------------
/workshops/2025-05/sections/08-api-endpoints/.gitignore:
--------------------------------------------------------------------------------
1 | baml_client/
2 | node_modules/
3 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/08-api-endpoints/baml_src/generators.baml:
--------------------------------------------------------------------------------
 1 | // This helps use auto generate libraries you can use in the language of
 2 | // your choice. You can have multiple generators if you use multiple languages.
 3 | // Just ensure that the output_dir is different for each generator.
 4 | generator target {
 5 |     // Valid values: "python/pydantic", "typescript", "ruby/sorbet", "rest/openapi"
 6 |     output_type "typescript"
 7 | 
 8 |     // Where the generated code will be saved (relative to baml_src/)
 9 |     output_dir "../"
10 | 
11 |     // The version of the BAML package you have installed (e.g. same version as your baml-py or @boundaryml/baml).
12 |     // The BAML VSCode extension version should also match this version.
13 |     version "0.85.0"
14 | 
15 |     // Valid values: "sync", "async"
16 |     // This controls what `b.FunctionName()` will be (sync or async).
17 |     default_client_mode async
18 | }
19 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/08-api-endpoints/baml_src/tool_calculator.baml:
--------------------------------------------------------------------------------
 1 | type CalculatorTools = AddTool | SubtractTool | MultiplyTool | DivideTool
 2 | 
 3 | 
 4 | class AddTool {
 5 |     intent "add"
 6 |     a int | float
 7 |     b int | float
 8 | }
 9 | 
10 | class SubtractTool {
11 |     intent "subtract"
12 |     a int | float
13 |     b int | float
14 | }
15 | 
16 | class MultiplyTool {
17 |     intent "multiply"
18 |     a int | float
19 |     b int | float
20 | }
21 | 
22 | class DivideTool {
23 |     intent "divide"
24 |     a int | float
25 |     b int | float
26 | }
27 | 
28 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/08-api-endpoints/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "name": "my-agent",
 3 |     "version": "0.1.0",
 4 |     "private": true,
 5 |     "scripts": {
 6 |         "dev": "tsx src/index.ts",
 7 |         "build": "tsc"
 8 |     },
 9 |     "dependencies": {
10 |         "baml": "^0.0.0",
11 |         "tsx": "^4.15.0",
12 |         "typescript": "^5.0.0"
13 |     },
14 |     "devDependencies": {
15 |         "@types/node": "^20.0.0",
16 |         "@typescript-eslint/eslint-plugin": "^6.0.0",
17 |         "@typescript-eslint/parser": "^6.0.0",
18 |         "eslint": "^8.0.0"
19 |     }
20 | }
21 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/08-api-endpoints/src/cli.ts:
--------------------------------------------------------------------------------
 1 | // cli.ts lets you invoke the agent loop from the command line
 2 | 
 3 | import { agentLoop, Thread, Event } from "../src/agent";
 4 | 
 5 | 
 6 | 
 7 | export async function cli() {
 8 |     // Get command line arguments, skipping the first two (node and script name)
 9 |     const args = process.argv.slice(2);
10 | 
11 |     if (args.length === 0) {
12 |         console.error("Error: Please provide a message as a command line argument");
13 |         process.exit(1);
14 |     }
15 | 
16 |     // Join all arguments into a single message
17 |     const message = args.join(" ");
18 | 
19 |     // Create a new thread with the user's message as the initial event
20 |     const thread = new Thread([{ type: "user_input", data: message }]);
21 | 
22 |     // Run the agent loop with the thread
23 |     const result = await agentLoop(thread);
24 |     let lastEvent = result.events.slice(-1)[0];
25 | 
26 |     while (lastEvent.data.intent === "request_more_information") {
27 |         const message = await askHuman(lastEvent.data.message);
28 |         thread.events.push({ type: "human_response", data: message });
29 |         const result = await agentLoop(thread);
30 |         lastEvent = result.events.slice(-1)[0];
31 |     }
32 | 
33 |     // print the final result
34 |     // optional - you could loop here too
35 |     console.log(lastEvent.data.message);
36 |     process.exit(0);
37 | }
38 | 
39 | async function askHuman(message: string) {
40 |     const readline = require('readline').createInterface({
41 |         input: process.stdin,
42 |         output: process.stdout
43 |     });
44 | 
45 |     return new Promise((resolve) => {
46 |         readline.question(`${message}\n> `, (answer: string) => {
47 |             resolve(answer);
48 |         });
49 |     });
50 | }
51 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/08-api-endpoints/src/index.ts:
--------------------------------------------------------------------------------
 1 | import { cli } from "./cli"
 2 | 
 3 | async function hello(): Promise<void> {
 4 |     console.log('hello, world!')
 5 | }
 6 | 
 7 | async function main() {
 8 |     await cli()
 9 | }
10 | 
11 | main().catch(console.error)


--------------------------------------------------------------------------------
/workshops/2025-05/sections/08-api-endpoints/tsconfig.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "compilerOptions": {
 3 |       "target": "ES2017",
 4 |       "lib": ["esnext"],
 5 |       "allowJs": true,
 6 |       "skipLibCheck": true,
 7 |       "strict": true,
 8 |       "noEmit": true,
 9 |       "esModuleInterop": true,
10 |       "module": "esnext",
11 |       "moduleResolution": "bundler",
12 |       "resolveJsonModule": true,
13 |       "isolatedModules": true,
14 |       "jsx": "preserve",
15 |       "incremental": true,
16 |       "plugins": [],
17 |       "paths": {
18 |         "@/*": ["./*"]
19 |       }
20 |     },
21 |     "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
22 |     "exclude": ["node_modules", "walkthrough"]
23 |   }
24 |   


--------------------------------------------------------------------------------
/workshops/2025-05/sections/08-api-endpoints/walkthrough/08-server.ts:
--------------------------------------------------------------------------------
 1 | import express from 'express';
 2 | import { Thread, agentLoop } from '../src/agent';
 3 | 
 4 | const app = express();
 5 | app.use(express.json());
 6 | app.set('json spaces', 2);
 7 | 
 8 | // POST /thread - Start new thread
 9 | app.post('/thread', async (req, res) => {
10 |     const thread = new Thread([{
11 |         type: "user_input",
12 |         data: req.body.message
13 |     }]);
14 |     const result = await agentLoop(thread);
15 |     res.json(result);
16 | });
17 | 
18 | // GET /thread/:id - Get thread status 
19 | app.get('/thread/:id', (req, res) => {
20 |     // optional - add state
21 |     res.status(404).json({ error: "Not implemented yet" });
22 | });
23 | 
24 | const port = process.env.PORT || 3000;
25 | app.listen(port, () => {
26 |     console.log(`Server running on port ${port}`);
27 | });
28 | 
29 | export { app };


--------------------------------------------------------------------------------
/workshops/2025-05/sections/09-state-management/.gitignore:
--------------------------------------------------------------------------------
1 | baml_client/
2 | node_modules/
3 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/09-state-management/baml_src/generators.baml:
--------------------------------------------------------------------------------
 1 | // This helps use auto generate libraries you can use in the language of
 2 | // your choice. You can have multiple generators if you use multiple languages.
 3 | // Just ensure that the output_dir is different for each generator.
 4 | generator target {
 5 |     // Valid values: "python/pydantic", "typescript", "ruby/sorbet", "rest/openapi"
 6 |     output_type "typescript"
 7 | 
 8 |     // Where the generated code will be saved (relative to baml_src/)
 9 |     output_dir "../"
10 | 
11 |     // The version of the BAML package you have installed (e.g. same version as your baml-py or @boundaryml/baml).
12 |     // The BAML VSCode extension version should also match this version.
13 |     version "0.85.0"
14 | 
15 |     // Valid values: "sync", "async"
16 |     // This controls what `b.FunctionName()` will be (sync or async).
17 |     default_client_mode async
18 | }
19 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/09-state-management/baml_src/tool_calculator.baml:
--------------------------------------------------------------------------------
 1 | type CalculatorTools = AddTool | SubtractTool | MultiplyTool | DivideTool
 2 | 
 3 | 
 4 | class AddTool {
 5 |     intent "add"
 6 |     a int | float
 7 |     b int | float
 8 | }
 9 | 
10 | class SubtractTool {
11 |     intent "subtract"
12 |     a int | float
13 |     b int | float
14 | }
15 | 
16 | class MultiplyTool {
17 |     intent "multiply"
18 |     a int | float
19 |     b int | float
20 | }
21 | 
22 | class DivideTool {
23 |     intent "divide"
24 |     a int | float
25 |     b int | float
26 | }
27 | 
28 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/09-state-management/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "name": "my-agent",
 3 |     "version": "0.1.0",
 4 |     "private": true,
 5 |     "scripts": {
 6 |         "dev": "tsx src/index.ts",
 7 |         "build": "tsc"
 8 |     },
 9 |     "dependencies": {
10 |         "baml": "^0.0.0",
11 |         "express": "^5.1.0",
12 |         "tsx": "^4.15.0",
13 |         "typescript": "^5.0.0"
14 |     },
15 |     "devDependencies": {
16 |         "@types/express": "^5.0.1",
17 |         "@types/node": "^20.0.0",
18 |         "@typescript-eslint/eslint-plugin": "^6.0.0",
19 |         "@typescript-eslint/parser": "^6.0.0",
20 |         "eslint": "^8.0.0",
21 |         "supertest": "^7.1.0"
22 |     }
23 | }
24 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/09-state-management/src/cli.ts:
--------------------------------------------------------------------------------
 1 | // cli.ts lets you invoke the agent loop from the command line
 2 | 
 3 | import { agentLoop, Thread, Event } from "../src/agent";
 4 | 
 5 | 
 6 | 
 7 | export async function cli() {
 8 |     // Get command line arguments, skipping the first two (node and script name)
 9 |     const args = process.argv.slice(2);
10 | 
11 |     if (args.length === 0) {
12 |         console.error("Error: Please provide a message as a command line argument");
13 |         process.exit(1);
14 |     }
15 | 
16 |     // Join all arguments into a single message
17 |     const message = args.join(" ");
18 | 
19 |     // Create a new thread with the user's message as the initial event
20 |     const thread = new Thread([{ type: "user_input", data: message }]);
21 | 
22 |     // Run the agent loop with the thread
23 |     const result = await agentLoop(thread);
24 |     let lastEvent = result.events.slice(-1)[0];
25 | 
26 |     while (lastEvent.data.intent === "request_more_information") {
27 |         const message = await askHuman(lastEvent.data.message);
28 |         thread.events.push({ type: "human_response", data: message });
29 |         const result = await agentLoop(thread);
30 |         lastEvent = result.events.slice(-1)[0];
31 |     }
32 | 
33 |     // print the final result
34 |     // optional - you could loop here too
35 |     console.log(lastEvent.data.message);
36 |     process.exit(0);
37 | }
38 | 
39 | async function askHuman(message: string) {
40 |     const readline = require('readline').createInterface({
41 |         input: process.stdin,
42 |         output: process.stdout
43 |     });
44 | 
45 |     return new Promise((resolve) => {
46 |         readline.question(`${message}\n> `, (answer: string) => {
47 |             resolve(answer);
48 |         });
49 |     });
50 | }
51 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/09-state-management/src/index.ts:
--------------------------------------------------------------------------------
 1 | import { cli } from "./cli"
 2 | 
 3 | async function hello(): Promise<void> {
 4 |     console.log('hello, world!')
 5 | }
 6 | 
 7 | async function main() {
 8 |     await cli()
 9 | }
10 | 
11 | main().catch(console.error)


--------------------------------------------------------------------------------
/workshops/2025-05/sections/09-state-management/src/server.ts:
--------------------------------------------------------------------------------
 1 | import express from 'express';
 2 | import { Thread, agentLoop } from '../src/agent';
 3 | 
 4 | const app = express();
 5 | app.use(express.json());
 6 | app.set('json spaces', 2);
 7 | 
 8 | // POST /thread - Start new thread
 9 | app.post('/thread', async (req, res) => {
10 |     const thread = new Thread([{
11 |         type: "user_input",
12 |         data: req.body.message
13 |     }]);
14 |     const result = await agentLoop(thread);
15 |     res.json(result);
16 | });
17 | 
18 | // GET /thread/:id - Get thread status 
19 | app.get('/thread/:id', (req, res) => {
20 |     // optional - add state
21 |     res.status(404).json({ error: "Not implemented yet" });
22 | });
23 | 
24 | const port = process.env.PORT || 3000;
25 | app.listen(port, () => {
26 |     console.log(`Server running on port ${port}`);
27 | });
28 | 
29 | export { app };


--------------------------------------------------------------------------------
/workshops/2025-05/sections/09-state-management/tsconfig.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "compilerOptions": {
 3 |       "target": "ES2017",
 4 |       "lib": ["esnext"],
 5 |       "allowJs": true,
 6 |       "skipLibCheck": true,
 7 |       "strict": true,
 8 |       "noEmit": true,
 9 |       "esModuleInterop": true,
10 |       "module": "esnext",
11 |       "moduleResolution": "bundler",
12 |       "resolveJsonModule": true,
13 |       "isolatedModules": true,
14 |       "jsx": "preserve",
15 |       "incremental": true,
16 |       "plugins": [],
17 |       "paths": {
18 |         "@/*": ["./*"]
19 |       }
20 |     },
21 |     "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
22 |     "exclude": ["node_modules", "walkthrough"]
23 |   }
24 |   


--------------------------------------------------------------------------------
/workshops/2025-05/sections/09-state-management/walkthrough/09-state.ts:
--------------------------------------------------------------------------------
 1 | import crypto from 'crypto';
 2 | import { Thread } from '../src/agent';
 3 | 
 4 | 
 5 | // you can replace this with any simple state management,
 6 | // e.g. redis, sqlite, postgres, etc
 7 | export class ThreadStore {
 8 |     private threads: Map<string, Thread> = new Map();
 9 |     
10 |     create(thread: Thread): string {
11 |         const id = crypto.randomUUID();
12 |         this.threads.set(id, thread);
13 |         return id;
14 |     }
15 |     
16 |     get(id: string): Thread | undefined {
17 |         return this.threads.get(id);
18 |     }
19 |     
20 |     update(id: string, thread: Thread): void {
21 |         this.threads.set(id, thread);
22 |     }
23 | }


--------------------------------------------------------------------------------
/workshops/2025-05/sections/10-human-approval/.gitignore:
--------------------------------------------------------------------------------
1 | baml_client/
2 | node_modules/
3 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/10-human-approval/baml_src/generators.baml:
--------------------------------------------------------------------------------
 1 | // This helps use auto generate libraries you can use in the language of
 2 | // your choice. You can have multiple generators if you use multiple languages.
 3 | // Just ensure that the output_dir is different for each generator.
 4 | generator target {
 5 |     // Valid values: "python/pydantic", "typescript", "ruby/sorbet", "rest/openapi"
 6 |     output_type "typescript"
 7 | 
 8 |     // Where the generated code will be saved (relative to baml_src/)
 9 |     output_dir "../"
10 | 
11 |     // The version of the BAML package you have installed (e.g. same version as your baml-py or @boundaryml/baml).
12 |     // The BAML VSCode extension version should also match this version.
13 |     version "0.85.0"
14 | 
15 |     // Valid values: "sync", "async"
16 |     // This controls what `b.FunctionName()` will be (sync or async).
17 |     default_client_mode async
18 | }
19 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/10-human-approval/baml_src/tool_calculator.baml:
--------------------------------------------------------------------------------
 1 | type CalculatorTools = AddTool | SubtractTool | MultiplyTool | DivideTool
 2 | 
 3 | 
 4 | class AddTool {
 5 |     intent "add"
 6 |     a int | float
 7 |     b int | float
 8 | }
 9 | 
10 | class SubtractTool {
11 |     intent "subtract"
12 |     a int | float
13 |     b int | float
14 | }
15 | 
16 | class MultiplyTool {
17 |     intent "multiply"
18 |     a int | float
19 |     b int | float
20 | }
21 | 
22 | class DivideTool {
23 |     intent "divide"
24 |     a int | float
25 |     b int | float
26 | }
27 | 
28 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/10-human-approval/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "name": "my-agent",
 3 |     "version": "0.1.0",
 4 |     "private": true,
 5 |     "scripts": {
 6 |         "dev": "tsx src/index.ts",
 7 |         "build": "tsc"
 8 |     },
 9 |     "dependencies": {
10 |         "baml": "^0.0.0",
11 |         "express": "^5.1.0",
12 |         "tsx": "^4.15.0",
13 |         "typescript": "^5.0.0"
14 |     },
15 |     "devDependencies": {
16 |         "@types/express": "^5.0.1",
17 |         "@types/node": "^20.0.0",
18 |         "@typescript-eslint/eslint-plugin": "^6.0.0",
19 |         "@typescript-eslint/parser": "^6.0.0",
20 |         "eslint": "^8.0.0",
21 |         "supertest": "^7.1.0"
22 |     }
23 | }
24 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/10-human-approval/src/cli.ts:
--------------------------------------------------------------------------------
 1 | // cli.ts lets you invoke the agent loop from the command line
 2 | 
 3 | import { agentLoop, Thread, Event } from "../src/agent";
 4 | 
 5 | 
 6 | 
 7 | export async function cli() {
 8 |     // Get command line arguments, skipping the first two (node and script name)
 9 |     const args = process.argv.slice(2);
10 | 
11 |     if (args.length === 0) {
12 |         console.error("Error: Please provide a message as a command line argument");
13 |         process.exit(1);
14 |     }
15 | 
16 |     // Join all arguments into a single message
17 |     const message = args.join(" ");
18 | 
19 |     // Create a new thread with the user's message as the initial event
20 |     const thread = new Thread([{ type: "user_input", data: message }]);
21 | 
22 |     // Run the agent loop with the thread
23 |     const result = await agentLoop(thread);
24 |     let lastEvent = result.events.slice(-1)[0];
25 | 
26 |     while (lastEvent.data.intent === "request_more_information") {
27 |         const message = await askHuman(lastEvent.data.message);
28 |         thread.events.push({ type: "human_response", data: message });
29 |         const result = await agentLoop(thread);
30 |         lastEvent = result.events.slice(-1)[0];
31 |     }
32 | 
33 |     // print the final result
34 |     // optional - you could loop here too
35 |     console.log(lastEvent.data.message);
36 |     process.exit(0);
37 | }
38 | 
39 | async function askHuman(message: string) {
40 |     const readline = require('readline').createInterface({
41 |         input: process.stdin,
42 |         output: process.stdout
43 |     });
44 | 
45 |     return new Promise((resolve) => {
46 |         readline.question(`${message}\n> `, (answer: string) => {
47 |             resolve(answer);
48 |         });
49 |     });
50 | }
51 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/10-human-approval/src/index.ts:
--------------------------------------------------------------------------------
 1 | import { cli } from "./cli"
 2 | 
 3 | async function hello(): Promise<void> {
 4 |     console.log('hello, world!')
 5 | }
 6 | 
 7 | async function main() {
 8 |     await cli()
 9 | }
10 | 
11 | main().catch(console.error)


--------------------------------------------------------------------------------
/workshops/2025-05/sections/10-human-approval/src/state.ts:
--------------------------------------------------------------------------------
 1 | import crypto from 'crypto';
 2 | import { Thread } from '../src/agent';
 3 | 
 4 | 
 5 | // you can replace this with any simple state management,
 6 | // e.g. redis, sqlite, postgres, etc
 7 | export class ThreadStore {
 8 |     private threads: Map<string, Thread> = new Map();
 9 |     
10 |     create(thread: Thread): string {
11 |         const id = crypto.randomUUID();
12 |         this.threads.set(id, thread);
13 |         return id;
14 |     }
15 |     
16 |     get(id: string): Thread | undefined {
17 |         return this.threads.get(id);
18 |     }
19 |     
20 |     update(id: string, thread: Thread): void {
21 |         this.threads.set(id, thread);
22 |     }
23 | }


--------------------------------------------------------------------------------
/workshops/2025-05/sections/10-human-approval/tsconfig.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "compilerOptions": {
 3 |       "target": "ES2017",
 4 |       "lib": ["esnext"],
 5 |       "allowJs": true,
 6 |       "skipLibCheck": true,
 7 |       "strict": true,
 8 |       "noEmit": true,
 9 |       "esModuleInterop": true,
10 |       "module": "esnext",
11 |       "moduleResolution": "bundler",
12 |       "resolveJsonModule": true,
13 |       "isolatedModules": true,
14 |       "jsx": "preserve",
15 |       "incremental": true,
16 |       "plugins": [],
17 |       "paths": {
18 |         "@/*": ["./*"]
19 |       }
20 |     },
21 |     "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
22 |     "exclude": ["node_modules", "walkthrough"]
23 |   }
24 |   


--------------------------------------------------------------------------------
/workshops/2025-05/sections/11-humanlayer-approval/.gitignore:
--------------------------------------------------------------------------------
1 | baml_client/
2 | node_modules/
3 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/11-humanlayer-approval/baml_src/generators.baml:
--------------------------------------------------------------------------------
 1 | // This helps use auto generate libraries you can use in the language of
 2 | // your choice. You can have multiple generators if you use multiple languages.
 3 | // Just ensure that the output_dir is different for each generator.
 4 | generator target {
 5 |     // Valid values: "python/pydantic", "typescript", "ruby/sorbet", "rest/openapi"
 6 |     output_type "typescript"
 7 | 
 8 |     // Where the generated code will be saved (relative to baml_src/)
 9 |     output_dir "../"
10 | 
11 |     // The version of the BAML package you have installed (e.g. same version as your baml-py or @boundaryml/baml).
12 |     // The BAML VSCode extension version should also match this version.
13 |     version "0.85.0"
14 | 
15 |     // Valid values: "sync", "async"
16 |     // This controls what `b.FunctionName()` will be (sync or async).
17 |     default_client_mode async
18 | }
19 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/11-humanlayer-approval/baml_src/tool_calculator.baml:
--------------------------------------------------------------------------------
 1 | type CalculatorTools = AddTool | SubtractTool | MultiplyTool | DivideTool
 2 | 
 3 | 
 4 | class AddTool {
 5 |     intent "add"
 6 |     a int | float
 7 |     b int | float
 8 | }
 9 | 
10 | class SubtractTool {
11 |     intent "subtract"
12 |     a int | float
13 |     b int | float
14 | }
15 | 
16 | class MultiplyTool {
17 |     intent "multiply"
18 |     a int | float
19 |     b int | float
20 | }
21 | 
22 | class DivideTool {
23 |     intent "divide"
24 |     a int | float
25 |     b int | float
26 | }
27 | 
28 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/11-humanlayer-approval/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "name": "my-agent",
 3 |     "version": "0.1.0",
 4 |     "private": true,
 5 |     "scripts": {
 6 |         "dev": "tsx src/index.ts",
 7 |         "build": "tsc"
 8 |     },
 9 |     "dependencies": {
10 |         "baml": "^0.0.0",
11 |         "express": "^5.1.0",
12 |         "tsx": "^4.15.0",
13 |         "typescript": "^5.0.0"
14 |     },
15 |     "devDependencies": {
16 |         "@types/express": "^5.0.1",
17 |         "@types/node": "^20.0.0",
18 |         "@typescript-eslint/eslint-plugin": "^6.0.0",
19 |         "@typescript-eslint/parser": "^6.0.0",
20 |         "eslint": "^8.0.0",
21 |         "supertest": "^7.1.0"
22 |     }
23 | }
24 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/11-humanlayer-approval/src/cli.ts:
--------------------------------------------------------------------------------
 1 | // cli.ts lets you invoke the agent loop from the command line
 2 | 
 3 | import { agentLoop, Thread, Event } from "../src/agent";
 4 | 
 5 | 
 6 | 
 7 | export async function cli() {
 8 |     // Get command line arguments, skipping the first two (node and script name)
 9 |     const args = process.argv.slice(2);
10 | 
11 |     if (args.length === 0) {
12 |         console.error("Error: Please provide a message as a command line argument");
13 |         process.exit(1);
14 |     }
15 | 
16 |     // Join all arguments into a single message
17 |     const message = args.join(" ");
18 | 
19 |     // Create a new thread with the user's message as the initial event
20 |     const thread = new Thread([{ type: "user_input", data: message }]);
21 | 
22 |     // Run the agent loop with the thread
23 |     const result = await agentLoop(thread);
24 |     let lastEvent = result.events.slice(-1)[0];
25 | 
26 |     while (lastEvent.data.intent === "request_more_information") {
27 |         const message = await askHuman(lastEvent.data.message);
28 |         thread.events.push({ type: "human_response", data: message });
29 |         const result = await agentLoop(thread);
30 |         lastEvent = result.events.slice(-1)[0];
31 |     }
32 | 
33 |     // print the final result
34 |     // optional - you could loop here too
35 |     console.log(lastEvent.data.message);
36 |     process.exit(0);
37 | }
38 | 
39 | async function askHuman(message: string) {
40 |     const readline = require('readline').createInterface({
41 |         input: process.stdin,
42 |         output: process.stdout
43 |     });
44 | 
45 |     return new Promise((resolve) => {
46 |         readline.question(`${message}\n> `, (answer: string) => {
47 |             resolve(answer);
48 |         });
49 |     });
50 | }
51 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/11-humanlayer-approval/src/index.ts:
--------------------------------------------------------------------------------
 1 | import { cli } from "./cli"
 2 | 
 3 | async function hello(): Promise<void> {
 4 |     console.log('hello, world!')
 5 | }
 6 | 
 7 | async function main() {
 8 |     await cli()
 9 | }
10 | 
11 | main().catch(console.error)


--------------------------------------------------------------------------------
/workshops/2025-05/sections/11-humanlayer-approval/src/state.ts:
--------------------------------------------------------------------------------
 1 | import crypto from 'crypto';
 2 | import { Thread } from '../src/agent';
 3 | 
 4 | 
 5 | // you can replace this with any simple state management,
 6 | // e.g. redis, sqlite, postgres, etc
 7 | export class ThreadStore {
 8 |     private threads: Map<string, Thread> = new Map();
 9 |     
10 |     create(thread: Thread): string {
11 |         const id = crypto.randomUUID();
12 |         this.threads.set(id, thread);
13 |         return id;
14 |     }
15 |     
16 |     get(id: string): Thread | undefined {
17 |         return this.threads.get(id);
18 |     }
19 |     
20 |     update(id: string, thread: Thread): void {
21 |         this.threads.set(id, thread);
22 |     }
23 | }


--------------------------------------------------------------------------------
/workshops/2025-05/sections/11-humanlayer-approval/tsconfig.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "compilerOptions": {
 3 |       "target": "ES2017",
 4 |       "lib": ["esnext"],
 5 |       "allowJs": true,
 6 |       "skipLibCheck": true,
 7 |       "strict": true,
 8 |       "noEmit": true,
 9 |       "esModuleInterop": true,
10 |       "module": "esnext",
11 |       "moduleResolution": "bundler",
12 |       "resolveJsonModule": true,
13 |       "isolatedModules": true,
14 |       "jsx": "preserve",
15 |       "incremental": true,
16 |       "plugins": [],
17 |       "paths": {
18 |         "@/*": ["./*"]
19 |       }
20 |     },
21 |     "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
22 |     "exclude": ["node_modules", "walkthrough"]
23 |   }
24 |   


--------------------------------------------------------------------------------
/workshops/2025-05/sections/12-humanlayer-webhook/.gitignore:
--------------------------------------------------------------------------------
1 | baml_client/
2 | node_modules/
3 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/12-humanlayer-webhook/baml_src/generators.baml:
--------------------------------------------------------------------------------
 1 | // This helps use auto generate libraries you can use in the language of
 2 | // your choice. You can have multiple generators if you use multiple languages.
 3 | // Just ensure that the output_dir is different for each generator.
 4 | generator target {
 5 |     // Valid values: "python/pydantic", "typescript", "ruby/sorbet", "rest/openapi"
 6 |     output_type "typescript"
 7 | 
 8 |     // Where the generated code will be saved (relative to baml_src/)
 9 |     output_dir "../"
10 | 
11 |     // The version of the BAML package you have installed (e.g. same version as your baml-py or @boundaryml/baml).
12 |     // The BAML VSCode extension version should also match this version.
13 |     version "0.85.0"
14 | 
15 |     // Valid values: "sync", "async"
16 |     // This controls what `b.FunctionName()` will be (sync or async).
17 |     default_client_mode async
18 | }
19 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/12-humanlayer-webhook/baml_src/tool_calculator.baml:
--------------------------------------------------------------------------------
 1 | type CalculatorTools = AddTool | SubtractTool | MultiplyTool | DivideTool
 2 | 
 3 | 
 4 | class AddTool {
 5 |     intent "add"
 6 |     a int | float
 7 |     b int | float
 8 | }
 9 | 
10 | class SubtractTool {
11 |     intent "subtract"
12 |     a int | float
13 |     b int | float
14 | }
15 | 
16 | class MultiplyTool {
17 |     intent "multiply"
18 |     a int | float
19 |     b int | float
20 | }
21 | 
22 | class DivideTool {
23 |     intent "divide"
24 |     a int | float
25 |     b int | float
26 | }
27 | 
28 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/12-humanlayer-webhook/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "name": "my-agent",
 3 |     "version": "0.1.0",
 4 |     "private": true,
 5 |     "scripts": {
 6 |         "dev": "tsx src/index.ts",
 7 |         "build": "tsc"
 8 |     },
 9 |     "dependencies": {
10 |         "baml": "^0.0.0",
11 |         "express": "^5.1.0",
12 |         "humanlayer": "^0.7.7",
13 |         "tsx": "^4.15.0",
14 |         "typescript": "^5.0.0"
15 |     },
16 |     "devDependencies": {
17 |         "@types/express": "^5.0.1",
18 |         "@types/node": "^20.0.0",
19 |         "@typescript-eslint/eslint-plugin": "^6.0.0",
20 |         "@typescript-eslint/parser": "^6.0.0",
21 |         "eslint": "^8.0.0",
22 |         "supertest": "^7.1.0"
23 |     }
24 | }
25 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/12-humanlayer-webhook/src/index.ts:
--------------------------------------------------------------------------------
 1 | import { cli } from "./cli"
 2 | 
 3 | async function hello(): Promise<void> {
 4 |     console.log('hello, world!')
 5 | }
 6 | 
 7 | async function main() {
 8 |     await cli()
 9 | }
10 | 
11 | main().catch(console.error)


--------------------------------------------------------------------------------
/workshops/2025-05/sections/12-humanlayer-webhook/src/state.ts:
--------------------------------------------------------------------------------
 1 | import crypto from 'crypto';
 2 | import { Thread } from '../src/agent';
 3 | 
 4 | 
 5 | // you can replace this with any simple state management,
 6 | // e.g. redis, sqlite, postgres, etc
 7 | export class ThreadStore {
 8 |     private threads: Map<string, Thread> = new Map();
 9 |     
10 |     create(thread: Thread): string {
11 |         const id = crypto.randomUUID();
12 |         this.threads.set(id, thread);
13 |         return id;
14 |     }
15 |     
16 |     get(id: string): Thread | undefined {
17 |         return this.threads.get(id);
18 |     }
19 |     
20 |     update(id: string, thread: Thread): void {
21 |         this.threads.set(id, thread);
22 |     }
23 | }


--------------------------------------------------------------------------------
/workshops/2025-05/sections/12-humanlayer-webhook/tsconfig.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "compilerOptions": {
 3 |       "target": "ES2017",
 4 |       "lib": ["esnext"],
 5 |       "allowJs": true,
 6 |       "skipLibCheck": true,
 7 |       "strict": true,
 8 |       "noEmit": true,
 9 |       "esModuleInterop": true,
10 |       "module": "esnext",
11 |       "moduleResolution": "bundler",
12 |       "resolveJsonModule": true,
13 |       "isolatedModules": true,
14 |       "jsx": "preserve",
15 |       "incremental": true,
16 |       "plugins": [],
17 |       "paths": {
18 |         "@/*": ["./*"]
19 |       }
20 |     },
21 |     "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
22 |     "exclude": ["node_modules", "walkthrough"]
23 |   }
24 |   


--------------------------------------------------------------------------------
/workshops/2025-05/sections/final/.gitignore:
--------------------------------------------------------------------------------
1 | baml_client/
2 | node_modules/
3 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/final/baml_src/clients.baml:
--------------------------------------------------------------------------------
 1 | // Learn more about clients at https://docs.boundaryml.com/docs/snippets/clients/overview
 2 | 
 3 | client<llm> CustomGPT4o {
 4 |   provider openai
 5 |   options {
 6 |     model "gpt-4o"
 7 |     api_key env.OPENAI_API_KEY
 8 |   }
 9 | }
10 | 
11 | client<llm> CustomGPT4oMini {
12 |   provider openai
13 |   retry_policy Exponential
14 |   options {
15 |     model "gpt-4o-mini"
16 |     api_key env.OPENAI_API_KEY
17 |   }
18 | }
19 | 
20 | client<llm> CustomSonnet {
21 |   provider anthropic
22 |   options {
23 |     model "claude-3-5-sonnet-20241022"
24 |     api_key env.ANTHROPIC_API_KEY
25 |   }
26 | }
27 | 
28 | 
29 | client<llm> CustomHaiku {
30 |   provider anthropic
31 |   retry_policy Constant
32 |   options {
33 |     model "claude-3-haiku-20240307"
34 |     api_key env.ANTHROPIC_API_KEY
35 |   }
36 | }
37 | 
38 | // https://docs.boundaryml.com/docs/snippets/clients/round-robin
39 | client<llm> CustomFast {
40 |   provider round-robin
41 |   options {
42 |     // This will alternate between the two clients
43 |     strategy [CustomGPT4oMini, CustomHaiku]
44 |   }
45 | }
46 | 
47 | // https://docs.boundaryml.com/docs/snippets/clients/fallback
48 | client<llm> OpenaiFallback {
49 |   provider fallback
50 |   options {
51 |     // This will try the clients in order until one succeeds
52 |     strategy [CustomGPT4oMini, CustomGPT4oMini]
53 |   }
54 | }
55 | 
56 | // https://docs.boundaryml.com/docs/snippets/clients/retry
57 | retry_policy Constant {
58 |   max_retries 3
59 |   // Strategy is optional
60 |   strategy {
61 |     type constant_delay
62 |     delay_ms 200
63 |   }
64 | }
65 | 
66 | retry_policy Exponential {
67 |   max_retries 2
68 |   // Strategy is optional
69 |   strategy {
70 |     type exponential_backoff
71 |     delay_ms 300
72 |     multiplier 1.5
73 |     max_delay_ms 10000
74 |   }
75 | }


--------------------------------------------------------------------------------
/workshops/2025-05/sections/final/baml_src/generators.baml:
--------------------------------------------------------------------------------
 1 | // This helps use auto generate libraries you can use in the language of
 2 | // your choice. You can have multiple generators if you use multiple languages.
 3 | // Just ensure that the output_dir is different for each generator.
 4 | generator target {
 5 |     // Valid values: "python/pydantic", "typescript", "ruby/sorbet", "rest/openapi"
 6 |     output_type "typescript"
 7 | 
 8 |     // Where the generated code will be saved (relative to baml_src/)
 9 |     output_dir "../"
10 | 
11 |     // The version of the BAML package you have installed (e.g. same version as your baml-py or @boundaryml/baml).
12 |     // The BAML VSCode extension version should also match this version.
13 |     version "0.85.0"
14 | 
15 |     // Valid values: "sync", "async"
16 |     // This controls what `b.FunctionName()` will be (sync or async).
17 |     default_client_mode async
18 | }
19 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/final/baml_src/tool_calculator.baml:
--------------------------------------------------------------------------------
 1 | type CalculatorTools = AddTool | SubtractTool | MultiplyTool | DivideTool
 2 | 
 3 | 
 4 | class AddTool {
 5 |     intent "add"
 6 |     a int | float
 7 |     b int | float
 8 | }
 9 | 
10 | class SubtractTool {
11 |     intent "subtract"
12 |     a int | float
13 |     b int | float
14 | }
15 | 
16 | class MultiplyTool {
17 |     intent "multiply"
18 |     a int | float
19 |     b int | float
20 | }
21 | 
22 | class DivideTool {
23 |     intent "divide"
24 |     a int | float
25 |     b int | float
26 | }
27 | 
28 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/final/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "name": "my-agent",
 3 |     "version": "0.1.0",
 4 |     "private": true,
 5 |     "scripts": {
 6 |         "dev": "tsx src/index.ts",
 7 |         "build": "tsc"
 8 |     },
 9 |     "dependencies": {
10 |         "baml": "^0.0.0",
11 |         "express": "^5.1.0",
12 |         "humanlayer": "^0.7.7",
13 |         "tsx": "^4.15.0",
14 |         "typescript": "^5.0.0"
15 |     },
16 |     "devDependencies": {
17 |         "@types/express": "^5.0.1",
18 |         "@types/node": "^20.0.0",
19 |         "@typescript-eslint/eslint-plugin": "^6.0.0",
20 |         "@typescript-eslint/parser": "^6.0.0",
21 |         "eslint": "^8.0.0",
22 |         "supertest": "^7.1.0"
23 |     }
24 | }
25 | 


--------------------------------------------------------------------------------
/workshops/2025-05/sections/final/src/index.ts:
--------------------------------------------------------------------------------
 1 | import { cli } from "./cli"
 2 | 
 3 | async function hello(): Promise<void> {
 4 |     console.log('hello, world!')
 5 | }
 6 | 
 7 | async function main() {
 8 |     await cli()
 9 | }
10 | 
11 | main().catch(console.error)


--------------------------------------------------------------------------------
/workshops/2025-05/sections/final/src/state.ts:
--------------------------------------------------------------------------------
 1 | import crypto from 'crypto';
 2 | import { Thread } from '../src/agent';
 3 | 
 4 | 
 5 | // you can replace this with any simple state management,
 6 | // e.g. redis, sqlite, postgres, etc
 7 | export class ThreadStore {
 8 |     private threads: Map<string, Thread> = new Map();
 9 |     
10 |     create(thread: Thread): string {
11 |         const id = crypto.randomUUID();
12 |         this.threads.set(id, thread);
13 |         return id;
14 |     }
15 |     
16 |     get(id: string): Thread | undefined {
17 |         return this.threads.get(id);
18 |     }
19 |     
20 |     update(id: string, thread: Thread): void {
21 |         this.threads.set(id, thread);
22 |     }
23 | }


--------------------------------------------------------------------------------
/workshops/2025-05/sections/final/tsconfig.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "compilerOptions": {
 3 |       "target": "ES2017",
 4 |       "lib": ["esnext"],
 5 |       "allowJs": true,
 6 |       "skipLibCheck": true,
 7 |       "strict": true,
 8 |       "noEmit": true,
 9 |       "esModuleInterop": true,
10 |       "module": "esnext",
11 |       "moduleResolution": "bundler",
12 |       "resolveJsonModule": true,
13 |       "isolatedModules": true,
14 |       "jsx": "preserve",
15 |       "incremental": true,
16 |       "plugins": [],
17 |       "paths": {
18 |         "@/*": ["./*"]
19 |       }
20 |     },
21 |     "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
22 |     "exclude": ["node_modules", "walkthrough"]
23 |   }
24 |   


--------------------------------------------------------------------------------
/workshops/2025-05/walkthrough/00-.gitignore:
--------------------------------------------------------------------------------
1 | baml_client/
2 | node_modules/
3 | 


--------------------------------------------------------------------------------
/workshops/2025-05/walkthrough/00-index.ts:
--------------------------------------------------------------------------------
1 | async function hello(): Promise<void> {
2 |     console.log('hello, world!')
3 | }
4 | 
5 | async function main() {
6 |     await hello()
7 | }
8 | 
9 | main().catch(console.error)


--------------------------------------------------------------------------------
/workshops/2025-05/walkthrough/00-package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "name": "my-agent",
 3 |     "version": "0.1.0",
 4 |     "private": true,
 5 |     "scripts": {
 6 |       "dev": "tsx src/index.ts",
 7 |       "build": "tsc"
 8 |     },
 9 |     "dependencies": {
10 |       "tsx": "^4.15.0",
11 |       "typescript": "^5.0.0"
12 |     },
13 |     "devDependencies": {
14 |       "@types/node": "^20.0.0",
15 |       "@typescript-eslint/eslint-plugin": "^6.0.0",
16 |       "@typescript-eslint/parser": "^6.0.0",
17 |       "eslint": "^8.0.0"
18 |     }
19 |   }
20 |   


--------------------------------------------------------------------------------
/workshops/2025-05/walkthrough/00-tsconfig.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "compilerOptions": {
 3 |       "target": "ES2017",
 4 |       "lib": ["esnext"],
 5 |       "allowJs": true,
 6 |       "skipLibCheck": true,
 7 |       "strict": true,
 8 |       "noEmit": true,
 9 |       "esModuleInterop": true,
10 |       "module": "esnext",
11 |       "moduleResolution": "bundler",
12 |       "resolveJsonModule": true,
13 |       "isolatedModules": true,
14 |       "jsx": "preserve",
15 |       "incremental": true,
16 |       "plugins": [],
17 |       "paths": {
18 |         "@/*": ["./*"]
19 |       }
20 |     },
21 |     "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
22 |     "exclude": ["node_modules", "walkthrough"]
23 |   }
24 |   


--------------------------------------------------------------------------------
/workshops/2025-05/walkthrough/01-agent.baml:
--------------------------------------------------------------------------------
 1 | class DoneForNow {
 2 |   intent "done_for_now"
 3 |   message string 
 4 | }
 5 | 
 6 | function DetermineNextStep(
 7 |     thread: string 
 8 | ) -> DoneForNow {
 9 |     client "openai/gpt-4o"
10 | 
11 |     prompt #"
12 |         {{ _.role("system") }}
13 | 
14 |         You are a helpful assistant that can help with tasks.
15 | 
16 |         {{ _.role("user") }}
17 | 
18 |         You are working on the following thread:
19 | 
20 |         {{ thread }}
21 | 
22 |         What should the next step be?
23 | 
24 |         {{ ctx.output_format }}
25 |     "#
26 | }
27 | 
28 | test HelloWorld {
29 |   functions [DetermineNextStep]
30 |   args {
31 |     thread #"
32 |       {
33 |         "type": "user_input",
34 |         "data": "hello!"
35 |       }
36 |     "#
37 |   }
38 | }


--------------------------------------------------------------------------------
/workshops/2025-05/walkthrough/01-agent.ts:
--------------------------------------------------------------------------------
 1 | import { b } from "../baml_client";
 2 | 
 3 | // tool call or a respond to human tool
 4 | type AgentResponse = Awaited<ReturnType<typeof b.DetermineNextStep>>;
 5 | 
 6 | export interface Event {
 7 |     type: string
 8 |     data: any;
 9 | }
10 | 
11 | export class Thread {
12 |     events: Event[] = [];
13 | 
14 |     constructor(events: Event[]) {
15 |         this.events = events;
16 |     }
17 | 
18 |     serializeForLLM() {
19 |         // can change this to whatever custom serialization you want to do, XML, etc
20 |         // e.g. https://github.com/got-agents/agents/blob/59ebbfa236fc376618f16ee08eb0f3bf7b698892/linear-assistant-ts/src/agent.ts#L66-L105
21 |         return JSON.stringify(this.events);
22 |     }
23 | }
24 | 
25 | // right now this just runs one turn with the LLM, but
26 | // we'll update this function to handle all the agent logic
27 | export async function agentLoop(thread: Thread): Promise<AgentResponse> {
28 |     const nextStep = await b.DetermineNextStep(thread.serializeForLLM());
29 |     return nextStep;
30 | }
31 | 
32 | 
33 | 


--------------------------------------------------------------------------------
/workshops/2025-05/walkthrough/01-cli.ts:
--------------------------------------------------------------------------------
 1 | // cli.ts lets you invoke the agent loop from the command line
 2 | 
 3 | import { agentLoop, Thread, Event } from "./agent";
 4 | 
 5 | export async function cli() {
 6 |     // Get command line arguments, skipping the first two (node and script name)
 7 |     const args = process.argv.slice(2);
 8 | 
 9 |     if (args.length === 0) {
10 |         console.error("Error: Please provide a message as a command line argument");
11 |         process.exit(1);
12 |     }
13 | 
14 |     // Join all arguments into a single message
15 |     const message = args.join(" ");
16 | 
17 |     // Create a new thread with the user's message as the initial event
18 |     const thread = new Thread([{ type: "user_input", data: message }]);
19 | 
20 |     // Run the agent loop with the thread
21 |     const result = await agentLoop(thread);
22 |     console.log(result);
23 | }
24 | 


--------------------------------------------------------------------------------
/workshops/2025-05/walkthrough/01-index.ts:
--------------------------------------------------------------------------------
 1 | import { cli } from "./cli"
 2 | 
 3 | async function hello(): Promise<void> {
 4 |     console.log('hello, world!')
 5 | }
 6 | 
 7 | async function main() {
 8 |     await cli()
 9 | }
10 | 
11 | main().catch(console.error)


--------------------------------------------------------------------------------
/workshops/2025-05/walkthrough/02-agent.baml:
--------------------------------------------------------------------------------
 1 | class DoneForNow {
 2 |   intent "done_for_now"
 3 |   message string 
 4 | }
 5 | 
 6 | function DetermineNextStep(
 7 |     thread: string 
 8 | ) -> CalculatorTools | DoneForNow {
 9 |     client "openai/gpt-4o"
10 | 
11 |     prompt #"
12 |         {{ _.role("system") }}
13 | 
14 |         You are a helpful assistant that can help with tasks.
15 | 
16 |         {{ _.role("user") }}
17 | 
18 |         You are working on the following thread:
19 | 
20 |         {{ thread }}
21 | 
22 |         What should the next step be?
23 | 
24 |         {{ ctx.output_format }}
25 |     "#
26 | }
27 | 
28 | test HelloWorld {
29 |   functions [DetermineNextStep]
30 |   args {
31 |     thread #"
32 |       {
33 |         "type": "user_input",
34 |         "data": "hello!"
35 |       }
36 |     "#
37 |   }
38 | }


--------------------------------------------------------------------------------
/workshops/2025-05/walkthrough/02-tool_calculator.baml:
--------------------------------------------------------------------------------
 1 | type CalculatorTools = AddTool | SubtractTool | MultiplyTool | DivideTool
 2 | 
 3 | 
 4 | class AddTool {
 5 |     intent "add"
 6 |     a int | float
 7 |     b int | float
 8 | }
 9 | 
10 | class SubtractTool {
11 |     intent "subtract"
12 |     a int | float
13 |     b int | float
14 | }
15 | 
16 | class MultiplyTool {
17 |     intent "multiply"
18 |     a int | float
19 |     b int | float
20 | }
21 | 
22 | class DivideTool {
23 |     intent "divide"
24 |     a int | float
25 |     b int | float
26 | }
27 | 
28 | 


--------------------------------------------------------------------------------
/workshops/2025-05/walkthrough/03-agent.ts:
--------------------------------------------------------------------------------
 1 | import { b } from "../baml_client";
 2 | 
 3 | // tool call or a respond to human tool
 4 | type AgentResponse = Awaited<ReturnType<typeof b.DetermineNextStep>>;
 5 | 
 6 | export interface Event {
 7 |     type: string
 8 |     data: any;
 9 | }
10 | 
11 | export class Thread {
12 |     events: Event[] = [];
13 | 
14 |     constructor(events: Event[]) {
15 |         this.events = events;
16 |     }
17 | 
18 |     serializeForLLM() {
19 |         // can change this to whatever custom serialization you want to do, XML, etc
20 |         // e.g. https://github.com/got-agents/agents/blob/59ebbfa236fc376618f16ee08eb0f3bf7b698892/linear-assistant-ts/src/agent.ts#L66-L105
21 |         return JSON.stringify(this.events);
22 |     }
23 | }
24 | 
25 | 
26 | 
27 | export async function agentLoop(thread: Thread): Promise<string> {
28 | 
29 |     while (true) {
30 |         const nextStep = await b.DetermineNextStep(thread.serializeForLLM());
31 |         console.log("nextStep", nextStep);
32 | 
33 |         switch (nextStep.intent) {
34 |             case "done_for_now":
35 |                 // response to human, return the next step object
36 |                 return nextStep.message;
37 |             case "add":
38 |                 thread.events.push({
39 |                     "type": "tool_call",
40 |                     "data": nextStep
41 |                 });
42 |                 const result = nextStep.a + nextStep.b;
43 |                 console.log("tool_response", result);
44 |                 thread.events.push({
45 |                     "type": "tool_response",
46 |                     "data": result
47 |                 });
48 |                 continue;
49 |             default:
50 |                 throw new Error(`Unknown intent: ${nextStep.intent}`);
51 |         }
52 |     }
53 | }
54 | 
55 | 
56 | 


--------------------------------------------------------------------------------
/workshops/2025-05/walkthrough/04-agent.baml:
--------------------------------------------------------------------------------
 1 | class DoneForNow {
 2 |   intent "done_for_now"
 3 |   message string 
 4 | }
 5 | 
 6 | function DetermineNextStep(
 7 |     thread: string 
 8 | ) -> CalculatorTools | DoneForNow {
 9 |     client "openai/gpt-4o"
10 | 
11 |     prompt #"
12 |         {{ _.role("system") }}
13 | 
14 |         You are a helpful assistant that can help with tasks.
15 | 
16 |         {{ _.role("user") }}
17 | 
18 |         You are working on the following thread:
19 | 
20 |         {{ thread }}
21 | 
22 |         What should the next step be?
23 | 
24 |         {{ ctx.output_format }}
25 |     "#
26 | }
27 | 
28 | test HelloWorld {
29 |   functions [DetermineNextStep]
30 |   args {
31 |     thread #"
32 |       {
33 |         "type": "user_input",
34 |         "data": "hello!"
35 |       }
36 |     "#
37 |   }
38 | }
39 | 
40 | test MathOperation {
41 |   functions [DetermineNextStep]
42 |   args {
43 |     thread #"
44 |       {
45 |         "type": "user_input",
46 |         "data": "can you multiply 3 and 4?"
47 |       }
48 |     "#
49 |   }
50 | }
51 | 
52 | 


--------------------------------------------------------------------------------
/workshops/2025-05/walkthrough/04b-agent.baml:
--------------------------------------------------------------------------------
 1 | class DoneForNow {
 2 |   intent "done_for_now"
 3 |   message string 
 4 | }
 5 | 
 6 | function DetermineNextStep(
 7 |     thread: string 
 8 | ) -> CalculatorTools | DoneForNow {
 9 |     client "openai/gpt-4o"
10 | 
11 |     prompt #"
12 |         {{ _.role("system") }}
13 | 
14 |         You are a helpful assistant that can help with tasks.
15 | 
16 |         {{ _.role("user") }}
17 | 
18 |         You are working on the following thread:
19 | 
20 |         {{ thread }}
21 | 
22 |         What should the next step be?
23 | 
24 |         {{ ctx.output_format }}
25 |     "#
26 | }
27 | 
28 | test HelloWorld {
29 |   functions [DetermineNextStep]
30 |   args {
31 |     thread #"
32 |       {
33 |         "type": "user_input",
34 |         "data": "hello!"
35 |       }
36 |     "#
37 |   }
38 |   @@assert(hello, {{this.intent == "done_for_now"}})
39 | }
40 | 
41 | test MathOperation {
42 |   functions [DetermineNextStep]
43 |   args {
44 |     thread #"
45 |       {
46 |         "type": "user_input",
47 |         "data": "can you multiply 3 and 4?"
48 |       }
49 |     "#
50 |   }
51 |   @@assert(math_operation, {{this.intent == "multiply"}})
52 | }
53 | 
54 | 


--------------------------------------------------------------------------------
/workshops/2025-05/walkthrough/05-cli.ts:
--------------------------------------------------------------------------------
 1 | // cli.ts lets you invoke the agent loop from the command line
 2 | 
 3 | import { agentLoop, Thread, Event } from "../src/agent";
 4 | 
 5 | 
 6 | 
 7 | export async function cli() {
 8 |     // Get command line arguments, skipping the first two (node and script name)
 9 |     const args = process.argv.slice(2);
10 | 
11 |     if (args.length === 0) {
12 |         console.error("Error: Please provide a message as a command line argument");
13 |         process.exit(1);
14 |     }
15 | 
16 |     // Join all arguments into a single message
17 |     const message = args.join(" ");
18 | 
19 |     // Create a new thread with the user's message as the initial event
20 |     const thread = new Thread([{ type: "user_input", data: message }]);
21 | 
22 |     // Run the agent loop with the thread
23 |     const result = await agentLoop(thread);
24 |     let lastEvent = result.events.slice(-1)[0];
25 | 
26 |     while (lastEvent.data.intent === "request_more_information") {
27 |         const message = await askHuman(lastEvent.data.message);
28 |         thread.events.push({ type: "human_response", data: message });
29 |         const result = await agentLoop(thread);
30 |         lastEvent = result.events.slice(-1)[0];
31 |     }
32 | 
33 |     // print the final result
34 |     // optional - you could loop here too
35 |     console.log(lastEvent.data.message);
36 |     process.exit(0);
37 | }
38 | 
39 | async function askHuman(message: string) {
40 |     const readline = require('readline').createInterface({
41 |         input: process.stdin,
42 |         output: process.stdout
43 |     });
44 | 
45 |     return new Promise((resolve) => {
46 |         readline.question(`${message}\n> `, (answer: string) => {
47 |             resolve(answer);
48 |         });
49 |     });
50 | }
51 | 


--------------------------------------------------------------------------------
/workshops/2025-05/walkthrough/08-server.ts:
--------------------------------------------------------------------------------
 1 | import express from 'express';
 2 | import { Thread, agentLoop } from '../src/agent';
 3 | 
 4 | const app = express();
 5 | app.use(express.json());
 6 | app.set('json spaces', 2);
 7 | 
 8 | // POST /thread - Start new thread
 9 | app.post('/thread', async (req, res) => {
10 |     const thread = new Thread([{
11 |         type: "user_input",
12 |         data: req.body.message
13 |     }]);
14 |     const result = await agentLoop(thread);
15 |     res.json(result);
16 | });
17 | 
18 | // GET /thread/:id - Get thread status 
19 | app.get('/thread/:id', (req, res) => {
20 |     // optional - add state
21 |     res.status(404).json({ error: "Not implemented yet" });
22 | });
23 | 
24 | const port = process.env.PORT || 3000;
25 | app.listen(port, () => {
26 |     console.log(`Server running on port ${port}`);
27 | });
28 | 
29 | export { app };


--------------------------------------------------------------------------------
/workshops/2025-05/walkthrough/09-state.ts:
--------------------------------------------------------------------------------
 1 | import crypto from 'crypto';
 2 | import { Thread } from '../src/agent';
 3 | 
 4 | 
 5 | // you can replace this with any simple state management,
 6 | // e.g. redis, sqlite, postgres, etc
 7 | export class ThreadStore {
 8 |     private threads: Map<string, Thread> = new Map();
 9 |     
10 |     create(thread: Thread): string {
11 |         const id = crypto.randomUUID();
12 |         this.threads.set(id, thread);
13 |         return id;
14 |     }
15 |     
16 |     get(id: string): Thread | undefined {
17 |         return this.threads.get(id);
18 |     }
19 |     
20 |     update(id: string, thread: Thread): void {
21 |         this.threads.set(id, thread);
22 |     }
23 | }


--------------------------------------------------------------------------------
/workshops/2025-05/walkthrough/11-email-approve.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humanlayer/12-factor-agents/d20c728368bf9c189d6d7aab704744decb6ec0cc/workshops/2025-05/walkthrough/11-email-approve.png


--------------------------------------------------------------------------------
/workshops/2025-05/walkthrough/11-email-custom.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humanlayer/12-factor-agents/d20c728368bf9c189d6d7aab704744decb6ec0cc/workshops/2025-05/walkthrough/11-email-custom.png


--------------------------------------------------------------------------------
/workshops/2025-05/walkthrough/11-email-reject.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humanlayer/12-factor-agents/d20c728368bf9c189d6d7aab704744decb6ec0cc/workshops/2025-05/walkthrough/11-email-reject.png


--------------------------------------------------------------------------------
/workshops/2025-07-16/.gitignore:
--------------------------------------------------------------------------------
1 | baml_src/*.baml
2 | src/*.ts
3 | package.json
4 | package-lock.json
5 | tsconfig.json
6 | build/
7 | tmp/
8 | 


--------------------------------------------------------------------------------
/workshops/2025-07-16/hack/minimal_test.ipynb:
--------------------------------------------------------------------------------
 1 | {
 2 |   "cells": [
 3 |     {
 4 |       "cell_type": "code",
 5 |       "execution_count": null,
 6 |       "metadata": {},
 7 |       "outputs": [],
 8 |       "source": [
 9 |         "import sys\n",
10 |         "print(\"Hello stdout!\")\n",
11 |         "print(\"Hello stderr!\", file=sys.stderr)\n",
12 |         "with open(\"test_output.txt\", \"w\") as f:\n",
13 |         "    f.write(\"Notebook executed successfully!\\n\")\n",
14 |         "print(\"✅ Test complete\")"
15 |       ]
16 |     }
17 |   ],
18 |   "metadata": {
19 |     "kernelspec": {
20 |       "display_name": "Python 3",
21 |       "language": "python",
22 |       "name": "python3"
23 |     },
24 |     "language_info": {
25 |       "name": "python",
26 |       "version": "3.8.0"
27 |     }
28 |   },
29 |   "nbformat": 4,
30 |   "nbformat_minor": 4
31 | }


--------------------------------------------------------------------------------
/workshops/2025-07-16/hack/test_log_capture.sh:
--------------------------------------------------------------------------------
 1 | #!/bin/bash
 2 | set -e
 3 | 
 4 | echo "🧪 Testing BAML Log Capture..."
 5 | 
 6 | # Clean up any previous test
 7 | rm -f test_capture.ipynb
 8 | rm -rf tmp/test_capture_*
 9 | 
10 | # Generate test notebook
11 | echo "📝 Generating test notebook..."
12 | uv run python walkthroughgen_py.py simple_log_test.yaml -o test_capture.ipynb
13 | 
14 | # Run in sim
15 | echo "🚀 Running test in sim..."
16 | ./test_notebook_colab_sim.sh test_capture.ipynb > /dev/null 2>&1
17 | 
18 | # Find the executed notebook in the timestamped directory
19 | NOTEBOOK_DIR=$(ls -1dt tmp/test_* | head -1)
20 | NOTEBOOK_PATH="$NOTEBOOK_DIR/test_notebook.ipynb"
21 | 
22 | echo "📋 Analyzing results from $NOTEBOOK_PATH..."
23 | 
24 | # First dump debug info
25 | echo "🔍 Dumping debug info..."
26 | python3 inspect_notebook.py "$NOTEBOOK_PATH" "run_with_baml_logs"
27 | 
28 | echo ""
29 | echo "📊 Running log capture analysis..."
30 | 
31 | # Check for BAML log patterns in the executed notebook
32 | python3 analyze_log_capture.py "$NOTEBOOK_PATH"
33 | 
34 | echo "🧹 Cleaning up..."
35 | rm -f test_capture.ipynb


--------------------------------------------------------------------------------
/workshops/2025-07-16/pyproject.toml:
--------------------------------------------------------------------------------
 1 | [project]
 2 | name = "workshops"
 3 | version = "0.1.0"
 4 | description = "Add your description here"
 5 | readme = "README.md"
 6 | requires-python = ">=3.11"
 7 | dependencies = [
 8 |     "baml>=0.19.1",
 9 |     "jupyter>=1.1.1",
10 |     "nbformat>=5.10.4",
11 |     "pyyaml>=6.0.2",
12 | ]
13 | 


--------------------------------------------------------------------------------
/workshops/2025-07-16/walkthrough/00-.gitignore:
--------------------------------------------------------------------------------
1 | baml_client/
2 | node_modules/
3 | 


--------------------------------------------------------------------------------
/workshops/2025-07-16/walkthrough/00-main.py:
--------------------------------------------------------------------------------
1 | def hello():
2 |     print('hello, world!')
3 | 
4 | def main():
5 |     hello()


--------------------------------------------------------------------------------
/workshops/2025-07-16/walkthrough/00-package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "name": "my-agent",
 3 |     "version": "0.1.0",
 4 |     "private": true,
 5 |     "scripts": {
 6 |       "dev": "tsx src/index.ts",
 7 |       "build": "tsc"
 8 |     },
 9 |     "dependencies": {
10 |       "tsx": "^4.15.0",
11 |       "typescript": "^5.0.0"
12 |     },
13 |     "devDependencies": {
14 |       "@types/node": "^20.0.0",
15 |       "@typescript-eslint/eslint-plugin": "^6.0.0",
16 |       "@typescript-eslint/parser": "^6.0.0",
17 |       "eslint": "^8.0.0"
18 |     }
19 |   }
20 |   


--------------------------------------------------------------------------------
/workshops/2025-07-16/walkthrough/00-tsconfig.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "compilerOptions": {
 3 |       "target": "ES2017",
 4 |       "lib": ["esnext"],
 5 |       "allowJs": true,
 6 |       "skipLibCheck": true,
 7 |       "strict": true,
 8 |       "noEmit": true,
 9 |       "esModuleInterop": true,
10 |       "module": "esnext",
11 |       "moduleResolution": "bundler",
12 |       "resolveJsonModule": true,
13 |       "isolatedModules": true,
14 |       "jsx": "preserve",
15 |       "incremental": true,
16 |       "plugins": [],
17 |       "paths": {
18 |         "@/*": ["./*"]
19 |       }
20 |     },
21 |     "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
22 |     "exclude": ["node_modules", "walkthrough"]
23 |   }
24 |   


--------------------------------------------------------------------------------
/workshops/2025-07-16/walkthrough/01-agent.baml:
--------------------------------------------------------------------------------
 1 | class DoneForNow {
 2 |   intent "done_for_now"
 3 |   message string 
 4 | }
 5 | 
 6 | function DetermineNextStep(
 7 |     thread: string 
 8 | ) -> DoneForNow {
 9 |     client "openai/gpt-4o"
10 | 
11 |     // use /nothink for now because the thinking tokens (or streaming thereof) screw with baml (i think (no pun intended))
12 |     prompt #"
13 |         {{ _.role("system") }}
14 | 
15 |         You are a helpful assistant that can help with tasks.
16 | 
17 |         {{ _.role("user") }}
18 | 
19 |         You are working on the following thread:
20 | 
21 |         {{ thread }}
22 | 
23 |         What should the next step be?
24 | 
25 |         {{ ctx.output_format }}
26 |     "#
27 | }
28 | 
29 | test HelloWorld {
30 |   functions [DetermineNextStep]
31 |   args {
32 |     thread #"
33 |       {
34 |         "type": "user_input",
35 |         "data": "hello!"
36 |       }
37 |     "#
38 |   }
39 | }


--------------------------------------------------------------------------------
/workshops/2025-07-16/walkthrough/01-agent.py:
--------------------------------------------------------------------------------
 1 | import json
 2 | from typing import Dict, Any, List
 3 | 
 4 | # tool call or a respond to human tool
 5 | AgentResponse = Any  # This will be the return type from b.DetermineNextStep
 6 | 
 7 | class Event:
 8 |     def __init__(self, type: str, data: Any):
 9 |         self.type = type
10 |         self.data = data
11 | 
12 | class Thread:
13 |     def __init__(self, events: List[Dict[str, Any]]):
14 |         self.events = events
15 |     
16 |     def serialize_for_llm(self):
17 |         # can change this to whatever custom serialization you want to do, XML, etc
18 |         # e.g. https://github.com/got-agents/agents/blob/59ebbfa236fc376618f16ee08eb0f3bf7b698892/linear-assistant-ts/src/agent.ts#L66-L105
19 |         return json.dumps(self.events)
20 | 
21 | # right now this just runs one turn with the LLM, but
22 | # we'll update this function to handle all the agent logic
23 | def agent_loop(thread: Thread) -> AgentResponse:
24 |     b = get_baml_client()  # This will be defined by the BAML setup
25 |     next_step = b.DetermineNextStep(thread.serialize_for_llm())
26 |     return next_step


--------------------------------------------------------------------------------
/workshops/2025-07-16/walkthrough/01-main.py:
--------------------------------------------------------------------------------
1 | def main(message="hello from the notebook!"):
2 |     # Create a new thread with the user's message as the initial event
3 |     thread = Thread([{"type": "user_input", "data": message}])
4 |     
5 |     # Run the agent loop with the thread
6 |     result = agent_loop(thread)
7 |     print(result)


--------------------------------------------------------------------------------
/workshops/2025-07-16/walkthrough/02-agent.baml:
--------------------------------------------------------------------------------
 1 | class DoneForNow {
 2 |   intent "done_for_now"
 3 |   message string 
 4 | }
 5 | 
 6 | function DetermineNextStep(
 7 |     thread: string 
 8 | ) -> CalculatorTools | DoneForNow {
 9 |     client "openai/gpt-4o"
10 | 
11 |     // use /nothink for now because the thinking tokens (or streaming thereof) screw with baml (i think (no pun intended))
12 |     prompt #"
13 |         {{ _.role("system") }}
14 | 
15 |         You are a helpful assistant that can help with tasks.
16 | 
17 |         {{ _.role("user") }}
18 | 
19 |         You are working on the following thread:
20 | 
21 |         {{ thread }}
22 | 
23 |         What should the next step be?
24 | 
25 |         {{ ctx.output_format }}
26 |     "#
27 | }
28 | 
29 | test HelloWorld {
30 |   functions [DetermineNextStep]
31 |   args {
32 |     thread #"
33 |       {
34 |         "type": "user_input",
35 |         "data": "hello!"
36 |       }
37 |     "#
38 |   }
39 | }


--------------------------------------------------------------------------------
/workshops/2025-07-16/walkthrough/02-main.py:
--------------------------------------------------------------------------------
 1 | def main(message="hello from the notebook!"):
 2 |     # Create a new thread with the user's message
 3 |     thread = Thread([{"type": "user_input", "data": message}])
 4 |     
 5 |     # Get BAML client
 6 |     b = get_baml_client()
 7 |     
 8 |     # Get the next step from the agent - just show the tool call
 9 |     next_step = b.DetermineNextStep(thread.serialize_for_llm())
10 |     
11 |     # Print the raw response to show the tool call
12 |     print(next_step)


--------------------------------------------------------------------------------
/workshops/2025-07-16/walkthrough/02-tool_calculator.baml:
--------------------------------------------------------------------------------
 1 | type CalculatorTools = AddTool | SubtractTool | MultiplyTool | DivideTool
 2 | 
 3 | 
 4 | class AddTool {
 5 |     intent "add"
 6 |     a int | float
 7 |     b int | float
 8 | }
 9 | 
10 | class SubtractTool {
11 |     intent "subtract"
12 |     a int | float
13 |     b int | float
14 | }
15 | 
16 | class MultiplyTool {
17 |     intent "multiply"
18 |     a int | float
19 |     b int | float
20 | }
21 | 
22 | class DivideTool {
23 |     intent "divide"
24 |     a int | float
25 |     b int | float
26 | }
27 | 
28 | 


--------------------------------------------------------------------------------
/workshops/2025-07-16/walkthrough/03-agent.py:
--------------------------------------------------------------------------------
 1 | import json
 2 | from typing import Dict, Any, List
 3 | 
 4 | class Thread:
 5 |     def __init__(self, events: List[Dict[str, Any]]):
 6 |         self.events = events
 7 |     
 8 |     def serialize_for_llm(self):
 9 |         # can change this to whatever custom serialization you want to do, XML, etc
10 |         # e.g. https://github.com/got-agents/agents/blob/59ebbfa236fc376618f16ee08eb0f3bf7b698892/linear-assistant-ts/src/agent.ts#L66-L105
11 |         return json.dumps(self.events)
12 | 
13 | 
14 | def agent_loop(thread: Thread) -> str:
15 |     b = get_baml_client()
16 |     
17 |     while True:
18 |         next_step = b.DetermineNextStep(thread.serialize_for_llm())
19 |         print("nextStep", next_step)
20 |         
21 |         if next_step.intent == "done_for_now":
22 |             # response to human, return the next step object
23 |             return next_step.message
24 |         elif next_step.intent == "add":
25 |             thread.events.append({
26 |                 "type": "tool_call",
27 |                 "data": next_step.__dict__
28 |             })
29 |             result = next_step.a + next_step.b
30 |             print("tool_response", result)
31 |             thread.events.append({
32 |                 "type": "tool_response",
33 |                 "data": result
34 |             })
35 |             continue
36 |         else:
37 |             raise ValueError(f"Unknown intent: {next_step.intent}")


--------------------------------------------------------------------------------
/workshops/2025-07-16/walkthrough/03-main.py:
--------------------------------------------------------------------------------
1 | def main(message="hello from the notebook!"):
2 |     # Create a new thread with the user's message
3 |     thread = Thread([{"type": "user_input", "data": message}])
4 |     
5 |     # Run the agent loop with full tool handling
6 |     result = agent_loop(thread)
7 |     
8 |     # Print the final response
9 |     print(f"\nFinal response: {result}")


--------------------------------------------------------------------------------
/workshops/2025-07-16/walkthrough/04-agent.baml:
--------------------------------------------------------------------------------
 1 | class DoneForNow {
 2 |   intent "done_for_now"
 3 |   message string 
 4 | }
 5 | 
 6 | class AddTool {
 7 |     intent "add"
 8 |     a int | float
 9 |     b int | float
10 | }
11 | 
12 | class SubtractTool {
13 |     intent "subtract"
14 |     a int | float
15 |     b int | float
16 | }
17 | 
18 | class MultiplyTool {
19 |     intent "multiply"
20 |     a int | float
21 |     b int | float
22 | }
23 | 
24 | class DivideTool {
25 |     intent "divide"
26 |     a int | float
27 |     b int | float
28 | }
29 | 
30 | function DetermineNextStep(
31 |     thread: string 
32 | ) -> DoneForNow | AddTool | SubtractTool | MultiplyTool | DivideTool {
33 |     client "openai/gpt-4o"
34 | 
35 |     prompt #"
36 |         {{ _.role("system") }}
37 | 
38 |         You are a helpful assistant that can help with tasks.
39 | 
40 |         {{ _.role("user") }}
41 | 
42 |         You are working on the following thread:
43 | 
44 |         {{ thread }}
45 | 
46 |         What should the next step be?
47 | 
48 |         {{ ctx.output_format }}
49 |     "#
50 | }
51 | 
52 | test HelloWorld {
53 |   functions [DetermineNextStep]
54 |   args {
55 |     thread #"
56 |       {
57 |         "type": "user_input",
58 |         "data": "hello!"
59 |       }
60 |     "#
61 |   }
62 | }
63 | 
64 | test SimpleMath {
65 |   functions [DetermineNextStep]
66 |   args {
67 |     thread #"
68 |       [{"type": "user_input", "data": "can you multiply 3 and 4"}]
69 |     "#
70 |   }
71 | }


--------------------------------------------------------------------------------
/workshops/2025-07-16/walkthrough/04b-agent.baml:
--------------------------------------------------------------------------------
 1 | class DoneForNow {
 2 |   intent "done_for_now"
 3 |   message string 
 4 | }
 5 | 
 6 | class AddTool {
 7 |     intent "add"
 8 |     a int | float
 9 |     b int | float
10 | }
11 | 
12 | class SubtractTool {
13 |     intent "subtract"
14 |     a int | float
15 |     b int | float
16 | }
17 | 
18 | class MultiplyTool {
19 |     intent "multiply"
20 |     a int | float
21 |     b int | float
22 | }
23 | 
24 | class DivideTool {
25 |     intent "divide"
26 |     a int | float
27 |     b int | float
28 | }
29 | 
30 | function DetermineNextStep(
31 |     thread: string 
32 | ) -> DoneForNow | AddTool | SubtractTool | MultiplyTool | DivideTool {
33 |     client "openai/gpt-4o"
34 | 
35 |     prompt #"
36 |         {{ _.role("system") }}
37 | 
38 |         You are a helpful assistant that can help with tasks.
39 | 
40 |         {{ _.role("user") }}
41 | 
42 |         You are working on the following thread:
43 | 
44 |         {{ thread }}
45 | 
46 |         What should the next step be?
47 | 
48 |         {{ ctx.output_format }}
49 |     "#
50 | }
51 | 
52 | test HelloWorld {
53 |   functions [DetermineNextStep]
54 |   args {
55 |     thread #"
56 |       {
57 |         "type": "user_input",
58 |         "data": "hello!"
59 |       }
60 |     "#
61 |   }
62 |   @@assert(intent_check, {{this.intent == "done_for_now"}})
63 | }
64 | 
65 | test SimpleMath {
66 |   functions [DetermineNextStep]
67 |   args {
68 |     thread #"
69 |       [{"type": "user_input", "data": "can you multiply 3 and 4"}]
70 |     "#
71 |   }
72 |   @@assert(intent_check, {{this.intent == "multiply"}})
73 |   @@assert(a_check, {{this.a == 3}})
74 |   @@assert(b_check, {{this.b == 4}})
75 | }


--------------------------------------------------------------------------------
/workshops/2025-07-16/walkthrough/05-agent.baml:
--------------------------------------------------------------------------------
 1 | class DoneForNow {
 2 |   intent "done_for_now"
 3 |   message string 
 4 | }
 5 | 
 6 | class AddTool {
 7 |     intent "add"
 8 |     a int | float
 9 |     b int | float
10 | }
11 | 
12 | class SubtractTool {
13 |     intent "subtract"
14 |     a int | float
15 |     b int | float
16 | }
17 | 
18 | class MultiplyTool {
19 |     intent "multiply"
20 |     a int | float
21 |     b int | float
22 | }
23 | 
24 | class DivideTool {
25 |     intent "divide"
26 |     a int | float
27 |     b int | float
28 | }
29 | 
30 | class ClarificationRequest {
31 |     intent "request_more_information"
32 |     message string @description("you can request more information from the user")
33 | }
34 | 
35 | function DetermineNextStep(
36 |     thread: string 
37 | ) -> DoneForNow | AddTool | SubtractTool | MultiplyTool | DivideTool | ClarificationRequest {
38 |     client "openai/gpt-4o"
39 | 
40 |     prompt #"
41 |         {{ _.role("system") }}
42 | 
43 |         You are a helpful assistant that can help with tasks.
44 | 
45 |         {{ _.role("user") }}
46 | 
47 |         You are working on the following thread:
48 | 
49 |         {{ thread }}
50 | 
51 |         What should the next step be?
52 | 
53 |         {{ ctx.output_format }}
54 |     "#
55 | }


--------------------------------------------------------------------------------
/workshops/2025-07-16/walkthrough/05-main.py:
--------------------------------------------------------------------------------
 1 | def get_human_input(prompt):
 2 |     """Get input from human, handling both Colab and local environments."""
 3 |     print(f"\n🤔 {prompt}")
 4 |     
 5 |     if IN_COLAB:
 6 |         # In Colab, use actual input
 7 |         response = input("Your response: ")
 8 |     else:
 9 |         # In local testing, return a fixed response
10 |         response = "I meant to multiply 3 and 4"
11 |         print(f"📝 [Auto-response for testing]: {response}")
12 |     
13 |     return response
14 | 
15 | def main(message="hello from the notebook!"):
16 |     # Function to handle clarification requests
17 |     def handle_clarification(question):
18 |         return get_human_input(f"The agent needs clarification: {question}")
19 |     
20 |     # Create a new thread with the user's message
21 |     thread = Thread([{"type": "user_input", "data": message}])
22 |     
23 |     print(f"🚀 Starting agent with message: '{message}'")
24 |     
25 |     # Run the agent loop
26 |     result = agent_loop(thread, handle_clarification)
27 |     
28 |     # Print the final response
29 |     print(f"\n✅ Final response: {result}")


--------------------------------------------------------------------------------
/workshops/2025-07-16/walkthrough/06-agent.baml:
--------------------------------------------------------------------------------
 1 | class DoneForNow {
 2 |   intent "done_for_now"
 3 |   message string 
 4 | }
 5 | 
 6 | class AddTool {
 7 |     intent "add"
 8 |     a int | float
 9 |     b int | float
10 | }
11 | 
12 | class SubtractTool {
13 |     intent "subtract"
14 |     a int | float
15 |     b int | float
16 | }
17 | 
18 | class MultiplyTool {
19 |     intent "multiply"
20 |     a int | float
21 |     b int | float
22 | }
23 | 
24 | class DivideTool {
25 |     intent "divide"
26 |     a int | float
27 |     b int | float
28 | }
29 | 
30 | class ClarificationRequest {
31 |     intent "request_more_information"
32 |     message string @description("you can request more information from the user")
33 | }
34 | 
35 | function DetermineNextStep(
36 |     thread: string 
37 | ) -> DoneForNow | AddTool | SubtractTool | MultiplyTool | DivideTool | ClarificationRequest {
38 |     client "openai/gpt-4o"
39 | 
40 |     prompt #"
41 |         {{ _.role("system") }}
42 | 
43 |         You are a helpful assistant that can help with tasks.
44 | 
45 |         {{ _.role("user") }}
46 | 
47 |         You are working on the following thread:
48 | 
49 |         {{ thread }}
50 | 
51 |         Before deciding on the next step, think through the situation:
52 |         1. What has been asked?
53 |         2. What information do I have?
54 |         3. What tools are available to me?
55 |         4. What is the most logical next step?
56 | 
57 |         <reasoning>
58 |         Think step by step about what needs to be done next.
59 |         </reasoning>
60 | 
61 |         What should the next step be?
62 | 
63 |         {{ ctx.output_format }}
64 |     "#
65 | }


--------------------------------------------------------------------------------
/workshops/2025-07-16/walkthrough/07-main.py:
--------------------------------------------------------------------------------
 1 | def main(message="hello from the notebook!", use_xml=True):
 2 |     # Function to handle clarification requests
 3 |     def handle_clarification(question):
 4 |         return get_human_input(f"The agent needs clarification: {question}")
 5 |     
 6 |     # Create a new thread with the user's message
 7 |     thread = Thread([{"type": "user_input", "data": message}])
 8 |     
 9 |     print(f"🚀 Starting agent with message: '{message}'")
10 |     print(f"📋 Using {'XML' if use_xml else 'JSON'} format for thread serialization")
11 |     
12 |     # Run the agent loop with XML serialization
13 |     result = agent_loop(thread, handle_clarification, use_xml=use_xml)
14 |     
15 |     # Print the final response
16 |     print(f"\n✅ Final response: {result}")


--------------------------------------------------------------------------------
/workshops/2025-07-16/walkthrough/08-server.ts:
--------------------------------------------------------------------------------
 1 | import express from 'express';
 2 | import { Thread, agentLoop } from '../src/agent';
 3 | 
 4 | const app = express();
 5 | app.use(express.json());
 6 | app.set('json spaces', 2);
 7 | 
 8 | // POST /thread - Start new thread
 9 | app.post('/thread', async (req, res) => {
10 |     const thread = new Thread([{
11 |         type: "user_input",
12 |         data: req.body.message
13 |     }]);
14 |     const result = await agentLoop(thread);
15 |     res.json(result);
16 | });
17 | 
18 | // GET /thread/:id - Get thread status 
19 | app.get('/thread/:id', (req, res) => {
20 |     // optional - add state
21 |     res.status(404).json({ error: "Not implemented yet" });
22 | });
23 | 
24 | const port = process.env.PORT || 3000;
25 | app.listen(port, () => {
26 |     console.log(`Server running on port ${port}`);
27 | });
28 | 
29 | export { app };


--------------------------------------------------------------------------------
/workshops/2025-07-16/walkthrough/09-state.ts:
--------------------------------------------------------------------------------
 1 | import crypto from 'crypto';
 2 | import { Thread } from '../src/agent';
 3 | 
 4 | 
 5 | // you can replace this with any simple state management,
 6 | // e.g. redis, sqlite, postgres, etc
 7 | export class ThreadStore {
 8 |     private threads: Map<string, Thread> = new Map();
 9 |     
10 |     create(thread: Thread): string {
11 |         const id = crypto.randomUUID();
12 |         this.threads.set(id, thread);
13 |         return id;
14 |     }
15 |     
16 |     get(id: string): Thread | undefined {
17 |         return this.threads.get(id);
18 |     }
19 |     
20 |     update(id: string, thread: Thread): void {
21 |         this.threads.set(id, thread);
22 |     }
23 | }


--------------------------------------------------------------------------------
/workshops/2025-07-16/walkthrough/11-email-approve.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humanlayer/12-factor-agents/d20c728368bf9c189d6d7aab704744decb6ec0cc/workshops/2025-07-16/walkthrough/11-email-approve.png


--------------------------------------------------------------------------------
/workshops/2025-07-16/walkthrough/11-email-custom.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humanlayer/12-factor-agents/d20c728368bf9c189d6d7aab704744decb6ec0cc/workshops/2025-07-16/walkthrough/11-email-custom.png


--------------------------------------------------------------------------------
/workshops/2025-07-16/walkthrough/11-email-reject.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/humanlayer/12-factor-agents/d20c728368bf9c189d6d7aab704744decb6ec0cc/workshops/2025-07-16/walkthrough/11-email-reject.png


--------------------------------------------------------------------------------