├── .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 | 
8 |
9 |
10 | 
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
--------------------------------------------------------------------------------