How to Build a Portable Embedded Agent with the Go SDK
In this tutorial, you will build a portable ChatBotKit agent in Go. The final result is a single executable that contains the agent logic, embedded skills, an obfuscated bot ID, and an obfuscated scoped token that can mint temporary sessions for one bot.
This is a portability-first pattern, not a strong secret-management design.
The important framing is this: the binary hides the bot ID and the scoped session-minting token from casual inspection and from tools like strings, but it does not provide strong secret protection. Anything a local executable can use can eventually be recovered by someone who controls that executable. The practical safety comes from using a narrowly scoped token that can only mint temporary sessions for one bot.
Learning Objectives
By the end of this tutorial, you will know how to:
- Embed skill definitions into a Go binary with
embed.FS - Obfuscate a bot ID and scoped token at build time with
go generate - Create a temporary ChatBotKit bot session at runtime
- Run a stateful agent using the short-lived session token returned by ChatBotKit
- Ship the whole agent as one portable executable
Prerequisites
Before you start, make sure you have:
- Go 1.21 or later
- A ChatBotKit account
- A bot you want to run through a portable binary
- Basic familiarity with Go
Estimated time: 20 to 30 minutes.
What You Are Building
The portable binary follows this runtime flow:
- Reveal the embedded bot ID and scoped minting token in memory.
- Create a ChatBotKit client with the scoped token.
- Call
bot/<botId>/session/create. - Receive a
conversationId, short-lived sessiontoken, andexpiresAttimestamp. - Create a second ChatBotKit client with the temporary session token.
- Run the stateful agent against the returned conversation.
The benefit is portability: you can copy one executable to another machine and run it without also copying a .env file, loose skill files, or separate config.
Step 1: Create a Scoped Session-Minting Token
Create a scoped token in ChatBotKit whose allowedRoutes contains only this route:
For example:
Do not include the /v1/ prefix in allowedRoutes. ChatBotKit matches route patterns against the API path without /v1/.
This is the critical safety property of the design. The embedded token should not be a full-access API token. It should only be able to mint sessions for one bot.
Step 2: Create the Project Structure
The example used for this tutorial lives in the Go SDK examples directory and uses this layout:
secrets_gen.go is generated from real values when you run go generate, but the checked-in example includes placeholder obfuscated values so the package still compiles before you add real credentials.
Step 3: Add Embedded Skills
Go's embed package lets you bake the skills/ directory directly into the binary:
At runtime, re-root the embedded filesystem and load the skills with the Go SDK:
Each skill directory needs a SKILL.md file with YAML front matter:
Step 4: Generate an Obfuscated Secret File
Instead of storing the bot ID and minting token as normal Go string constants, generate a file that stores XOR-obfuscated byte arrays.
The generator reads two environment variables:
CHATBOTKIT_BOT_IDCHATBOTKIT_SESSION_MINT_TOKEN
Then it writes secrets_gen.go with a data byte slice and a mask byte slice for each secret.
The generator is wired into go generate:
The core obfuscation logic is simple:
At runtime, the binary reverses that XOR to recover the values in memory.
This keeps the raw values out of plain string tables, but it is not cryptographic protection. Anyone with enough control over the binary can still recover what it can use.
Step 5: Reveal the Secrets at Runtime
The runtime Reveal helper decodes each embedded secret:
The example also refuses to run if the checked-in placeholder values are still present.
Step 6: Mint a Temporary Bot Session
Once the binary has the bot ID and scoped token, it creates a temporary session:
That call returns:
conversationIdtokenexpiresAt
The returned token is the short-lived credential the stateful agent will actually use.
Step 7: Run the Stateful Agent
Create a second SDK client with the temporary session token, then run the agent against the returned conversation:
This keeps the long-lived embedded credential limited to session creation while the actual agent run happens with the short-lived token returned by ChatBotKit.
Step 8: Build the Portable Binary
From the example directory, set your real values and generate the secret file:
Then build the binary:
Run it with a custom task:
Or run it with the built-in demo task:
Step 9: Verify the Binary Is Self-Contained
Because the skills are embedded and the credentials are generated into Go source, the compiled binary does not depend on an external .env file or a separate skills/ directory at runtime.
You can also sanity-check that the raw strings are not present in the final executable:
If you regenerated the secret file with real values and rebuilt, those exact strings should not appear in plain text.
That still does not make the embedded credential unrecoverable. It only removes the most obvious plaintext exposure.
Troubleshooting
403 Forbidden when creating the session
- Verify the scoped token allows exactly
bot/YOUR_BOT_ID/session/create. - Make sure the route in
allowedRoutesdoes not include/v1/. - Confirm the embedded bot ID matches the bot the token was scoped for.
The binary exits with a placeholder-secret error
- You built the example without regenerating
secrets_gen.go. - Export
CHATBOTKIT_BOT_IDandCHATBOTKIT_SESSION_MINT_TOKEN, then rungo generate .again.
Skills do not load
- Confirm each skill lives in its own subdirectory under
skills/. - Make sure each
SKILL.mdfile includesnameanddescriptionin YAML front matter.
The example builds but fails at runtime on another machine
- The design assumes the target machine can reach the ChatBotKit API.
- If the task uses local tools like shell execution, those tools still depend on the local machine environment.
Why This Pattern Is Useful
This approach works well when you want to move one binary between machines without carrying extra files around.
For example:
That convenience is the main value. The tradeoff is that the embedded minting credential must be treated as recoverable, so it should stay narrowly scoped.
Next Steps
- Start from the verified example in
sdks/go/examples/portable-embedded-agent - Add more embedded skills to the
skills/directory - Replace
agent.DefaultTools()with a smaller tool set if you want a more restricted binary - Move session creation behind your own backend if you need stronger credential protection