The agent's tools and settings
Tools
| Tool | Description | Parameters |
|---|---|---|
| balatro_exec | Run async JavaScript against Balatro through balatrobot; `bal` is in scope. Use `return <value>` for results. Read the state (`await bal.state()`), then act: `bal.select()`, `bal.skip()`, `bal.play([i, ...])`, `bal.discard([i, ...])`, `bal.cashOut()`, `bal.buy({card|voucher|pack: i})`, `bal.sell({joker|consumable: i})`, `bal.reroll()`, `bal.pack({card: i, targets} | {skip: true})`, `bal.use(i, [targets])`, `bal.rearrange({hand|jokers|consumables: [order]})`, `bal.nextRound()`, and after a game over `bal.restart()`. Every action returns the state the game settled in. |
|
| balatro_documentation | Return the complete supported JavaScript API reference for the `bal` object used inside balatro_exec. Call this when exact methods, arguments, option fields, or result types are needed. | none |
| balatro_screenshot | Capture a full-resolution screenshot of Balatro. By default it is returned as an image. Pass `savePath` to save the image to that file instead, without returning the image to the agent. |
|
Instructions
AGENTS.mdBalatro, AI assisted
You play Balatro through three tools: balatro_documentation (read it first), balatro_exec (JavaScript with bal in scope) and balatro_screenshot. The game is turn-based and waits between your actions; take the time you need, but every action counts. Play the run that has been started for you until the goal in your first message is reached (the whole game: beat the boss blind of ante 8). If the run ends in a game over, call bal.restart(): it starts a new run with the same deck and stake; the new run counts from the beginning and the session clock keeps running. When the goal is reached the session ends by itself. Do not start runs any other way, do not change settings, and do not look up information outside the game.
What the documentation tool returned
documentation.mdThe whole text
Balatro API
bal is in scope in balatro_exec. Balatro is turn-based and waits for you: nothing happens between your actions. Every action returns the game state after the game settled.
State
await bal.state() (alias bal.observe()) returns the game state as balatrobot reports it:
state: where the game is.BLIND_SELECT(choose to play or skip the next blind),SELECTING_HAND(play or discard cards),ROUND_EVAL(the round is won; cash out),SHOP,SMODS_BOOSTER_OPENED(a booster pack is open; pick from it or skip),GAME_OVER.ante_num,round_num,money,deck,stake,seed,won(true once the boss blind of ante 8 is beaten).round:hands_left,hands_played,discards_left,discards_used,reroll_cost,chips(scored this round).blinds:small,big,boss, each withtype,status(SELECT,CURRENT,UPCOMING,DEFEATED,SKIPPED),name,effect,score(chips needed),tag_name,tag_effect(the tag for skipping it).hands: per poker hand itslevel,chips,mult,played,played_this_round.- Card areas, each
{ count, limit, highlighted_limit, cards }:hand,jokers,consumables,cards(the deck), and in the shopshop,vouchers,packs;packwhen a booster pack is open. Areas that are empty and not always present are left out. - A card:
id,key(for exampleH_Afor the Ace of Hearts,j_joker,c_fool),set,label,value(suit,rank,effect: what it does, in the game's words),modifier(seal,edition,enhancement,eternal,perishable,rental),state(debuff,hidden,highlight),cost(buy,sell). used_vouchers.
Suits are H, D, C, S; ranks 2-9, T, J, Q, K, A.
Actions
Indices are 0-based positions in the area as the state lists it.
await bal.select(): play the blind that is up (BLIND_SELECT).await bal.skip(): skip the small or big blind for its tag (BLIND_SELECT). The boss blind cannot be skipped.await bal.play([i, ...]): play 1 to 5 cards fromhand(SELECTING_HAND).await bal.discard([i, ...]): discard cards fromhand(SELECTING_HAND).await bal.cashOut(): collect the round's reward and go to the shop (ROUND_EVAL).await bal.buy({ card: i }),bal.buy({ voucher: i }),bal.buy({ pack: i }): buy fromshop,vouchersorpacks(SHOP). A bought pack opens at once.await bal.pack({ card: i }): take cardifrom the open pack;bal.pack({ card: i, targets: [j, ...] })when that card needs cards fromhandas targets;bal.pack({ skip: true })to take nothing (SMODS_BOOSTER_OPENED). Tarot, Planet and Spectral cards from a pack are used at once; Jokers go tojokers, playing cards to the deck.await bal.sell({ joker: i }),bal.sell({ consumable: i }).await bal.use(i, [j, ...]?): use consumablei, with cards fromhandas targets when it needs them.await bal.rearrange({ hand: [...] }),bal.rearrange({ jokers: [...] }),bal.rearrange({ consumables: [...] }): the new order, as a permutation of the current indices. Joker order matters for some Jokers.await bal.reroll(): reroll the shop forround.reroll_cost(SHOP).await bal.nextRound(): leave the shop (SHOP).await bal.screenshot(): the game window as an image (the state is the ground truth; the picture is for orientation).await bal.restart(): only after a game over (stateGAME_OVER): a new run with the same deck and stake. Every other action is refused until you restart.
An action that does not fit the game's state or rules is refused with an error that says why; read the state first. The run has already been started for you (deck, stake and seed are fixed); starting runs, returning to the menu, saving, loading and changing the game's values are not available.
Runtime and game settings
UPSTREAM.json
game-config
- balatro
- steam_app_id
- 2 379 780
- tested_version
- 1.0.1o-FULL
- lovely
- repo
- https://github.com/ethangreen-dev/lovely-injector
- version
- 0.9.0
- url
- https://github.com/ethangreen-dev/lovely-injector/releases/download/v0.9.0/lovely-x86_64-pc-windows-msvc.zip
- sha256
- 40b994a055ee75e5f2aba81e7ae06f2c17460e18cc346483089921899fadd1f7
- file
- version.dll
- license
- MIT
- steamodded
- repo
- https://github.com/Steamodded/smods
- version
- 1.0.0-beta-1814a
- commit
- f8e4ae060df0e42ce698ffaf901a39a6ef424c0f
- url
- https://codeload.github.com/Steamodded/smods/zip/f8e4ae060df0e42ce698ffaf901a39a6ef424c0f
- sha256
- 1cae496bfd1c5dbfbc5db1eb51137d822c44fb7758ba6beaf020e43f1b5f96db
- folder
- smods
- license
- GPL-3.0
- balatrobot
- repo
- https://github.com/coder/balatrobot
- version
- 1.5.2
- commit
- 9052d76f14723293f6c6b2cecaa791a5c4ae68f3
- url
- https://codeload.github.com/coder/balatrobot/zip/refs/tags/v1.5.2
- sha256
- 7b1352be6b79d3190a3ab7cc0c9a88b3af7d5247a0dc8a8be09252b0ee241d5e
- folder
- balatrobot
- files
- balatrobot.jsonbalatrobot.luaLICENSEsrc/lua/
- settings
6 fields
- BALATROBOT_AUDIO
- 1
- BALATROBOT_FAST
- 0
- BALATROBOT_HEADLESS
- 0
- BALATROBOT_RENDER_ON_API
- 0
- BALATROBOT_DEBUG
- 0
- BALATROBOT_GAMESPEED
- 4
- license
- MIT
- jackdaw
- repo
- https://github.com/TylerFlar/jackdaw-balatro
- license
- MIT, Copyright (c) 2026 Tyler Flar
- used_by
- bot.mjs (the scripted player's policy, ported from jackdaw/cli/smart_agent.py)
- note
- Not shipped and not installed: a 1:1 Python reimplementation of Balatro with a bit-exact PRNG, used to measure the policy over seeds before it touches the game. Its own validation bridge plays against live Balatro through the same balatrobot API this plugin uses.
mcp.template.json
runtime-config
- mcpServers
- balatro
3 fields
- command
- node
- args
- --permission--allow-fs-read=__REPO__/packages/core--allow-fs-read=__REPO__/games/balatro--allow-fs-read=__RUN_DIR__--allow-fs-write=__RUN_DIR____REPO__/packages/core/src/broker.mjs
- env
3 fields
- AAS_GAME_MODULE
- __REPO__/games/balatro/plugin.mjs
- AAS_RUN_DIR
- __RUN_DIR__
- AAS_ALLOWED_ENDPOINTS
- 127.0.0.1:12347
settings.json
runtime-config
- permissions
- allow
- mcp__balatro__balatro_documentationmcp__balatro__balatro_screenshotmcp__balatro__balatro_exec
- deny
- BashWebFetchWebSearchAgentReadEditWriteNotebookEditGlobGrepSkillTodoWrite
- defaultMode
- acceptEdits
- enableAllProjectMcpServers
- yes
- enabledMcpjsonServers
- balatro