back to tutorials

How to Build a Portable Embedded Agent with the Go SDK

Learn how to package a ChatBotKit Go agent as a single executable with embedded skills, an obfuscated bot ID, and a narrowly scoped session-minting token.

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:

  1. Reveal the embedded bot ID and scoped minting token in memory.
  2. Create a ChatBotKit client with the scoped token.
  3. Call bot/<botId>/session/create.
  4. Receive a conversationId, short-lived session token, and expiresAt timestamp.
  5. Create a second ChatBotKit client with the temporary session token.
  6. 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_ID
  • CHATBOTKIT_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:

  • conversationId
  • token
  • expiresAt

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 allowedRoutes does 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_ID and CHATBOTKIT_SESSION_MINT_TOKEN, then run go generate . again.

Skills do not load

  • Confirm each skill lives in its own subdirectory under skills/.
  • Make sure each SKILL.md file includes name and description in 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