Model or dataset
charmbracelet/fantasy avatar
charmbracelet/fantasy

charmbracelet/fantasy: A Go Agent Library Behind Crush

Build AI agents with Go. Multiple providers, multiple models, one API. 🧙

990 stars137 forksGoApache-2.0

At a glance

What is it?
Fantasy puts several model providers behind one Go API, with tools and a system prompt attached to an agent value. It is a pre-1.0 library built to power Charm's own coding agent, and its scope stops at text and tool calls.
Who is it for?
Adopt Fantasy if you are writing a Go program that needs to call several model providers through one interface and you can tolerate a fast-moving pre-1.0 dependency, since the release history shows patch versions appearing within days of each other. Do not adopt it for image, audio or PDF work, because the README states those are not supported yet, and do not adopt it as a stable API contract for a library other teams compile against.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 1 day ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.

DEEP OPEN-SOURCE ANALYSIS

The problem: provider SDKs multiply as soon as you support two models

Any Go program that talks to more than one model vendor ends up with the same shape of problem. Each vendor ships its own client, its own request struct, its own streaming type and its own error values. The application code that matters, the part that decides when to call a tool and what to do with the answer, gets buried under adapter code that exists only because two HTTP APIs disagree about field names. Fantasy's stated goal is to collapse that: the README describes it as multi-provider, multi-model, one API. The intended reader is a Go developer building an agent, not a chat wrapper. The README's own three-step description is choose a model and provider, add some tools, compile to native machine code. That last step is the part a hosted agent framework cannot offer, and it is the reason someone would pick a Go library over a service. Fantasy is also not a general-purpose abstraction looking for users. The README states it was built to power Crush, Charm's coding agent, which explains why the scope is narrow and the missing features are the ones a coding agent does not need.

How the API is assembled: provider, model, tools, agent

The mechanism is visible in the README example. A provider is constructed first, with credentials passed as an option: openrouter.New(openrouter.WithAPIKey(myHotKey)). That call returns a provider value and an error. The provider then hands back a language model by name, provider.LanguageModel(ctx, "moonshotai/kimi-k2"), which is where the model identifier lives. Tools are separate values created with fantasy.NewAgentTool, taking a name, a description string and a function. The agent is then assembled with fantasy.NewAgent(model, ...) plus functional options, fantasy.WithSystemPrompt and fantasy.WithTools, and invoked with agent.Generate(ctx, fantasy.AgentCall{Prompt: prompt}). The result exposes the response text through result.Response.Content.Text(). Two design decisions stand out. First, the model is a value passed into the agent rather than something the agent resolves internally, so switching providers is a change at construction time and not a change to the call site. Second, tool registration is an option on the agent, which means the tool set is fixed when the agent is built rather than negotiated per call. Whether that is enough for agents that need to add or remove tools at runtime cannot be determined from the supplied material.

Provider coverage and the openaicompat fallback

The README names Microsoft Azure, Amazon Bedrock and OpenRouter as providers with dedicated packages in Fantasy, and says many others work through openaicompat, described as the generic OpenAI-compatible layer. That is the pragmatic split: a thin package per provider where the wire format genuinely differs, and one compatibility layer for everything that copied the OpenAI request shape. The README invites issues or pull requests when a provider is not compatible and needs special treatment, which tells you the maintainers expect gaps rather than a complete matrix. The practical consequence for an adopter is that provider support is not uniform. Some providers get a real package with their own authentication and endpoint handling, and others get whatever openaicompat can express. If your provider's API has a feature that does not map onto the OpenAI shape, the compatibility layer is where it will be lost, and the README does not document which features survive that mapping. The example uses OpenRouter with a Moonshot model identifier, which is a reminder that the provider and the model vendor are separate choices in this API.

What the README explicitly excludes

The section headed Work in Progress is short and specific. Fantasy does not yet support image models, audio models or PDF uploads. That is a real boundary, not a caveat. A program that needs to send a PDF to a model, transcribe audio or generate an image cannot do it through this library as documented, and there is no stated workaround. The README frames the omissions as a consequence of the project's origin: it was built to power Crush, a coding agent, so the features a coding agent does not need were not built. That is a coherent explanation and also a warning about the roadmap. Features arrive when the originating use case needs them or when someone sends a pull request. The README says PRs are welcome for things you would like to see supported, which places the burden on users rather than on a published plan. There is also no homepage listed for the repository, so the README and the examples directory are the documentation surface.

Getting it running and the module path to watch

The README gives the imports directly: charm.land/fantasy and charm.land/fantasy/providers/openrouter. The example constructs the provider, checks the error, creates a context, resolves the model, builds a tool with fantasy.NewAgentTool, assembles the agent with fantasy.NewAgent and the WithSystemPrompt and WithTools options, then calls agent.Generate with a fantasy.AgentCall containing the prompt. Credentials arrive through a provider constructor option, openrouter.WithAPIKey. The README points to the examples directory in the repository for the full implementation and more. The one thing to check before copying any of this is the module path. The README imports charm.land/fantasy, while the repository is github.com/charmbracelet/fantasy, and the GoDoc badge in the README also points at pkg.go.dev/charm.land/fantasy. That is a custom domain import path, which normally means a go.mod that declares the charm.land path plus a vanity redirect. If you vendor dependencies or your build environment restricts outbound module fetches, confirm the redirect resolves before you commit to the import path, because a mismatch between the repository URL and the declared module path is the kind of thing that surfaces at build time rather than at review time.

Release cadence is the real maintenance cost

The supplied release list shows v0.43.1 on 2026-09-07, v0.43.0 on 2026-09-04 and v0.42.1 on 2026-09-01. Three releases in seven days, all in the 0.4x line, and the last push to the repository is 2026-09-10. This is an actively developed library that has not reached 1.0. The version numbers alone tell you the maintainers reserve the right to change the API, and the gap between patch releases tells you changes land quickly. For an application that pins a version and upgrades deliberately, that is manageable. For a library that other teams import, or for a plugin ecosystem where third parties compile against your types, it is a liability, because your public surface inherits theirs. Budget for reading release notes before each bump rather than assuming patch-level changes are inert. The licence is Apache-2.0, which is a permissive licence that permits commercial and closed-source use and includes an explicit patent grant. It also carries notice and attribution obligations when you redistribute, and the file-level terms are what govern, so read the LICENSE and NOTICE files in the repository you actually pull rather than relying on the SPDX identifier. Nothing here is legal advice.

The alternative: vendor SDKs, and what you give up by not using them

The obvious alternative is to import each vendor's official Go SDK directly and write your own small interface over the two or three you actually use. The difference is not effort at the start, it is where the effort lands. With vendor SDKs you get the vendor's own types, its own streaming implementation and its own release cadence tied to the API it wraps, and you get new provider features the day they ship rather than when a third party maps them. What you own is the adapter layer, and you own it permanently: every new provider, every field the vendor adds, every authentication change. Fantasy inverts that. You own one interface and the maintainers own the adapters, but you also inherit their coverage gaps, their release timing and their decision about which provider features are worth exposing. For a single-provider Go program, the vendor SDK is the smaller dependency and the better fit. Fantasy starts to pay for itself at the point where the second provider appears, because that is where the adapter code you would otherwise write stops being trivial. A second real alternative is to call the provider over plain HTTP yourself, which is what openaicompat effectively does for OpenAI-shaped APIs, but then you are reimplementing what the library already ships. The trade is control against maintenance, and Fantasy only wins it if you genuinely need more than one provider.

Who this fits, and the first thing to verify

Fantasy fits a Go program that needs to reach several model providers through one interface, that treats text and tool calls as the whole job, and that can absorb a pre-1.0 dependency moving in weekly increments. It fits teams already in the Charm ecosystem, since the library exists to serve Crush and shares its assumptions. It does not fit multimodal work, because the README rules out image, audio and PDF support, and it does not fit anyone who needs a frozen API for third parties to compile against. The first thing to verify is not a benchmark or a feature list. It is whether your provider is served by a dedicated package or by openaicompat, and whether the specific capability you need survives that path. The second is the module path: confirm that charm.land/fantasy resolves and that the go.mod in the tag you pin declares the same path your imports use. Do both before you write the agent, because both are cheap to check and expensive to discover after the tool layer is built.

Editorial conclusion

Adopt Fantasy if you are writing a Go program that needs to call several model providers through one interface and you can tolerate a fast-moving pre-1.0 dependency, since the release history shows patch versions appearing within days of each other. Do not adopt it for image, audio or PDF work, because the README states those are not supported yet, and do not adopt it as a stable API contract for a library other teams compile against. Before committing, verify that your chosen provider has a dedicated package or works through openaicompat, and check the module path in the version of the repository you pull: the README imports charm.land/fantasy while the repository lives at github.com/charmbracelet/fantasy.

Official sources

  1. charmbracelet/fantasy on GitHub
  2. Issues
  3. License: Apache-2.0
  4. README
  5. Releases
Community notes

Community notes