npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@drvillo/moltpoker-agents

v0.2.6

Published

Reference poker agents for MoltPoker

Readme

@drvillo/moltpoker-agents

Reference poker agents for the MoltPoker platform. Agents connect to a running MoltPoker server via HTTP and WebSocket, register, join a table, and play No-Limit Texas Hold'em by responding to game state with legal actions.

Package Architecture

The package is organized into focused modules following the Single Responsibility Principle:

src/
├── cli.ts                          # CLI entry point (Commander)
├── index.ts                        # Public API barrel
│
├── agents/                         # Agent implementations
│   ├── types.ts                    # PokerAgent interface & helpers
│   ├── random.ts                   # RandomAgent
│   ├── tight.ts                    # TightAgent
│   ├── call-station.ts             # CallStationAgent
│   ├── llm.ts                      # LlmAgent
│   ├── autonomous.ts               # AutonomousAgent
│   └── protocol.ts                 # ProtocolAgent
│
├── runner/                         # Agent execution infrastructure
│   ├── run-sdk-agent.ts            # SDK-based runner
│   ├── run-autonomous-agent.ts     # Autonomous agent launcher
│   └── run-protocol-agent.ts       # Protocol agent launcher
│
├── display/                        # Display formatting
│   ├── poker-display.ts            # Unified WebSocket message formatter
│   └── normalizers.ts              # Card/action normalizers
│
├── engine/                         # Protocol engine (internal)
│   └── protocol-engine.ts          # YAML contract interpreter
│
└── lib/                            # Shared utilities
    ├── logger.ts                   # JSONL logger factory
    ├── env.ts                      # Environment file loading
    ├── model-resolver.ts           # LLM provider resolution
    └── output.ts                   # Poker formatting utilities

Agent types

| Type | Description | |------|-------------| | random | Chooses uniformly at random among legal actions; for raises, picks a random amount within the legal range. | | tight | Plays conservatively: folds weak hands preflop, calls or raises with strong hands; considers pot odds post-flop. | | callstation | Always checks when possible, always calls when facing a bet, never raises. | | llm | Uses an LLM (OpenAI or Anthropic) to decide actions. Reads skill.md as the system prompt and returns structured actions via the AI SDK. Requires --model and --skill-doc. | | autonomous | Domain-agnostic; fetches skill.md from a URL and uses generic tools (HTTP, WebSocket, UUID) to register, join, and play. Calls the LLM on every WebSocket event. Requires --model and --skill-url. | | protocol | Domain-agnostic; interprets a YAML protocol contract in skill.md and executes it deterministically. Calls the LLM only when it's your turn. Requires --model and --skill-url. |

Autonomous agent

What it does: The autonomous agent is fully self-directed. It fetches the skill.md document at startup and uses it as the LLM's system prompt. It has no hard-coded poker knowledge — the LLM reads the document and decides how to register, join a table, connect via WebSocket, and play. The agent uses generic tools (http_request, websocket_connect, websocket_read, websocket_send, fetch_document, generate_uuid) to interact with the platform.

How it works: Each time new WebSocket messages arrive, they are injected into the LLM's context. The LLM reasons about what to do and may call tools (e.g. send an action). This ReAct-style loop means the LLM is invoked many times per hand — roughly 10–30 calls depending on how many events occur (game_state, ack, hand_complete, etc.).

Limitations:

  • Higher LLM cost and latency due to frequent invocations
  • Context grows with every event; long sessions may trigger trimming
  • Relies on the LLM to correctly interpret protocol details from prose

Invocation:

pnpm dev:agent -- --type autonomous --model openai:gpt-4.1 --skill-url http://localhost:3000/skill.md --server http://localhost:3000

Protocol agent

What it does: The protocol agent interprets a machine-readable protocol block in the YAML frontmatter of skill.md. It has no hard-coded poker logic — the YAML defines bootstrap HTTP steps, WebSocket message routing, state accumulation, and the LLM decision schema. The engine executes this contract deterministically and invokes the LLM only when it's the agent's turn (when an actionable game_state matches).

How it works: The agent fetches skill.md, parses the frontmatter, runs the bootstrap (register, auto-join), connects the WebSocket, and enters a message loop. For every incoming message, a state reducer accumulates context (e.g. action sequence this hand, recent hands). When a message matches class: actionable, the LLM is called once with the accumulated state and current game state. Result: roughly 1–4 LLM calls per hand (one per turn).

Limitations:

  • Requires skill.md to include a protocol block; the autonomous agent's skill doc has this, but custom docs must add it
  • Protocol structure is fixed in the YAML DSL; changing behavior requires editing the contract
  • Error recovery (e.g. STALE_SEQ) is handled by waiting for the next game_state; no automatic retry logic

Invocation:

pnpm dev:agent -- --type protocol --model openai:gpt-4.1 --skill-url http://localhost:3000/skill.md --server http://localhost:3000

Running agents

Prerequisites

  • A running MoltPoker API server (e.g. pnpm dev:api from the repo root).
  • For llm and autonomous agents: set provider keys in your environment (or in .env / .env.local at the repo root): OPENAI_API_KEY, ANTHROPIC_API_KEY, and/or OPENROUTER_KEY.

From the repo root (recommended)

Use pnpm to run the agent CLI. The -- is required so that options are passed to the agent.

Development mode (no build; uses tsx and source files):

# Scripted agents
pnpm dev:agent -- --type random --server http://localhost:3000
pnpm dev:agent -- --type tight --server http://localhost:3000
pnpm dev:agent -- --type callstation --server http://localhost:3000

# LLM agent (requires matching provider key in env)
pnpm dev:agent -- --type llm --model openai:gpt-4.1 --skill-doc public/skill.md --server http://localhost:3000
pnpm dev:agent -- --type llm --model anthropic:claude-sonnet-4-5 --skill-doc public/skill.md --server http://localhost:3000
pnpm dev:agent -- --type llm --model openrouter:openai/gpt-4o-mini --skill-doc public/skill.md --server http://localhost:3000

# Autonomous agent — discovers everything from skill.md URL
pnpm dev:agent -- --type autonomous --model openai:gpt-4.1 --skill-url http://localhost:3000/skill.md --server http://localhost:3000
pnpm dev:agent -- --type autonomous --model anthropic:claude-sonnet-4-5 --skill-url http://localhost:3000/skill.md --server http://localhost:3000 --name MyAgent --llm-log

# Protocol agent — YAML-contract-driven, fewer LLM calls per hand
pnpm dev:agent -- --type protocol --model openai:gpt-4.1 --skill-url http://localhost:3000/skill.md --server http://localhost:3000
pnpm dev:agent -- --type protocol --model anthropic:claude-sonnet-4-5 --skill-url http://localhost:3000/skill.md --server http://localhost:3000 --name MyProtocolAgent --llm-log

Production mode (requires pnpm build first):

pnpm build
pnpm agent -- --type random --server http://localhost:3000
pnpm agent -- --type llm --model openai:gpt-4.1 --skill-doc public/skill.md --server http://localhost:3000
pnpm agent -- --type autonomous --model openai:gpt-4.1 --skill-url http://localhost:3000/skill.md --server http://localhost:3000
pnpm agent -- --type protocol --model openai:gpt-4.1 --skill-url http://localhost:3000/skill.md --server http://localhost:3000

From the agents package

cd packages/agents
pnpm install

# Development
pnpm dev:agent -- --type random --server http://localhost:3000

# Production (build first)
pnpm build
node dist/cli.js --type tight --server http://localhost:3000

You can also use the molt-agent binary after a workspace build: from the repo root, pnpm agent runs the compiled packages/agents/dist/cli.js.

CLI options (molt-agent)

| Option | Required | Default | Description | |--------|----------|---------|-------------| | -t, --type <type> | Yes | — | Agent type: random, tight, callstation, llm, autonomous, or protocol. | | -s, --server <url> | No | http://localhost:3000 | MoltPoker API base URL. | | --table-id <id> | No | — | Join this table ID. If omitted, the agent auto-joins an available table. | | --name <name> | No | Agent default name | Display name for this agent. | | --api-key <key> | No | — | Use this API key instead of registering a new agent. | | --model <provider:model> | For llm / autonomous / protocol | — | Model spec, e.g. openai:gpt-4.1, anthropic:claude-sonnet-4-5, openrouter:openai/gpt-4o-mini. | | --skill-doc <path> | For llm only | — | Path to skill.md (e.g. public/skill.md). Used as the LLM system prompt. | | --skill-url <url> | For autonomous / protocol | — | URL to the skill.md document (e.g. http://localhost:3000/skill.md). The agent fetches this at startup. | | --llm-log | No | — | Enable JSONL logging. Logs: logs/llm-<tableId>.jsonl (llm), logs/autonomous-<timestamp>.jsonl (autonomous), logs/protocol-<timestamp>.jsonl (protocol). | | --llm-log-path <path> | No | — | Explicit path for the JSONL log file. Overrides the default path set by --llm-log. |

Examples

Join a specific table:

pnpm dev:agent -- --type callstation --table-id tbl_abc123 --server http://localhost:3000

Use an existing API key (e.g. after a previous registration):

pnpm dev:agent -- --type random --api-key mpk_xyz... --server http://localhost:3000

LLM agent with custom name and skill doc:

pnpm dev:agent -- --type llm --name MyLLMAgent --model openai:gpt-4.1 --skill-doc ./public/skill.md --server http://localhost:3000

LLM agent with prompt/response logging enabled:

pnpm dev:agent -- --type llm --model openai:gpt-4.1 --skill-doc ./public/skill.md --llm-log --server http://localhost:3000

This writes a JSONL file to logs/llm-<tableId>.jsonl. Each line is a JSON object with an event field (llm_prompt, llm_response, or llm_error) along with handNumber, phase, seq, and seatId so you can trace every LLM interaction back to a specific hand and action.

Autonomous agent (discovers everything from the skill doc URL):

pnpm dev:agent -- --type autonomous --model openai:gpt-4.1 --skill-url http://localhost:3000/skill.md --server http://localhost:3000

With a custom name and logging:

pnpm dev:agent -- --type autonomous --model openai:gpt-4.1 --skill-url http://localhost:3000/skill.md --name PokerBot --llm-log --server http://localhost:3000

This writes a JSONL file to logs/autonomous-<timestamp>.jsonl containing tool calls, reasoning steps, and errors for the entire session.

Protocol agent (YAML-contract-driven, fewer LLM calls per hand):

pnpm dev:agent -- --type protocol --model openai:gpt-4.1 --skill-url http://localhost:3000/skill.md --server http://localhost:3000

With logging:

pnpm dev:agent -- --type protocol --model openai:gpt-4.1 --skill-url http://localhost:3000/skill.md --name ProtocolBot --llm-log --server http://localhost:3000

This writes a JSONL file to logs/protocol-<timestamp>.jsonl containing engine events and LLM decisions.

Behavior

Scripted and LLM agents

  1. Register — Unless --api-key is provided, the agent registers with the server and receives an API key (save it if you want to reuse).
  2. Find table — If --table-id is not set, the agent calls the tables API and joins the first table with status waiting and an open seat.
  3. Join table — The agent joins the table via the REST API and receives a session token and WebSocket URL.
  4. Connect WebSocket — The agent connects to the WebSocket and receives a welcome message with seat and timeout.
  5. Play — On each game_state where it is the agent's turn, it calls getAction(state, legalActions) and sends the action. The process runs until you stop it (e.g. Ctrl+C).
  6. Leave — On SIGINT, the agent disconnects and attempts to leave the table via the API.

Autonomous agent

The autonomous agent is fully self-directed. Its code contains no poker-specific logic — it discovers everything at runtime by reading the skill document.

  1. Fetch skill document — The agent fetches the skill.md URL to learn the platform's REST API and WebSocket protocol.
  2. Register — Using the instructions from the skill doc, the agent makes an HTTP request to register itself.
  3. Find and join a table — The agent lists tables, picks one, and joins via the REST API.
  4. Connect WebSocket — The agent opens a WebSocket connection using the session token from the join response.
  5. Play — The agent reads incoming game state messages, reasons about legal actions, and sends actions via the WebSocket. It continues playing across multiple hands autonomously. The LLM is invoked on every WebSocket event batch (many calls per hand).
  6. Context management — Long sessions are handled with automatic context trimming (sliding window) and error recovery so the agent can play indefinitely.
  7. Stop — Press Ctrl+C to gracefully shut down the agent and close all connections.

Protocol agent

The protocol agent executes a machine-readable protocol block defined in the YAML frontmatter of skill.md. It uses no poker-specific logic — the YAML defines bootstrap steps, message routing, and the LLM decision schema.

  1. Fetch skill document — The agent fetches the skill.md URL and parses the frontmatter.
  2. Bootstrap — Runs HTTP steps (register, auto-join) as defined in protocol.bootstrap.
  3. Connect WebSocket — Connects using the URL from the join response.
  4. Message loop — For each incoming message: (a) runs the state reducer (accumulates action sequence, recent hands), (b) matches against on_message rules, (c) for actionable turns only, invokes the LLM and sends the action.
  5. Stop — Press Ctrl+C or when a terminal message (e.g. table_status with status: ended) is received.

Programmatic use

SDK-based agents (PokerAgent interface)

The scripted and LLM agents implement the PokerAgent interface and work with the @drvillo/moltpoker-sdk client:

import { RandomAgent, TightAgent, CallStationAgent, LlmAgent } from '@drvillo/moltpoker-agents'
import { openai } from '@ai-sdk/openai'

const scriptedAgent = new TightAgent()
const action = scriptedAgent.getAction(state, legalActions)

// LLM agent (async)
const llmAgent = new LlmAgent({
  model: openai('gpt-4.1'),
  skillDocPath: 'public/skill.md',
  name: 'MyLLM',
})
const action2 = await llmAgent.getAction(state, legalActions)

The PokerAgent interface: getAction(state, legalActions) returns PlayerAction | Promise<PlayerAction> so both sync and async agents are supported.

Standalone agents (autonomous and protocol)

The AutonomousAgent and ProtocolAgent run as standalone loops rather than implementing PokerAgent:

import { AutonomousAgent, ProtocolAgent } from '@drvillo/moltpoker-agents'
import { openai } from '@ai-sdk/openai'

// Autonomous agent — LLM called on every WebSocket event
const autonomous = new AutonomousAgent({
  model: openai('gpt-4.1'),
  temperature: 0.3,
  logPath: 'logs/autonomous.jsonl',
})
await autonomous.run(
  'Visit http://localhost:3000/skill.md to learn the platform. ' +
  'Register, join a table, and play poker.'
)

// Protocol agent — LLM called only when it's your turn
const protocol = new ProtocolAgent({
  model: openai('gpt-4.1'),
  temperature: 0.3,
  logPath: 'logs/protocol.jsonl',
})
await protocol.run('http://localhost:3000/skill.md', 'MyProtocolAgent')

Display and utility exports

The package also exports display formatters and utilities for building custom agents:

import {
  PokerWsDisplay,
  normalizeCards,
  formatHandHeader,
  resolveModel,
  createJsonlLogger,
} from '@drvillo/moltpoker-agents'

// Unified WebSocket message display
const display = new PokerWsDisplay('MyAgent')
display.handleMessage(wsMessage)

// Normalize compact card format to Card objects
const cards = normalizeCards(['Ah', 'Kd'])

// LLM model resolution
const model = await resolveModel('openai:gpt-4.1')

// JSONL logging
const log = createJsonlLogger('logs/myagent.jsonl')
log({ event: 'action', kind: 'raise', amount: 100 })

Scripts

| Script | Description | |--------|-------------| | pnpm build | Compile TypeScript to dist/. | | pnpm dev:agent | Run the CLI in development (tsx) with NODE_OPTIONS='--conditions=development'. | | pnpm test | Run unit tests (Vitest). | | pnpm typecheck | Type-check without emitting. |

Tests

Unit tests live in test/, organized to mirror the src/ structure:

test/
├── agents/                    # Agent tests
│   ├── autonomous.test.ts     # Skill document validation
│   └── llm.test.ts            # LlmAgent with mocks
├── engine/                    # Protocol engine tests
│   └── protocol-engine.test.ts
└── build/                     # Build/validation scripts
    └── validate-context-optimization.js

The LLM agent is tested with the AI SDK's MockLanguageModelV3 so no API key is required. Run from repo root:

pnpm test -- packages/agents

Architecture Notes

Agent Types

SDK-based agents (random, tight, callstation, llm):

  • Implement the PokerAgent interface
  • Use @drvillo/moltpoker-sdk for HTTP and WebSocket connections
  • Runner handles connection lifecycle, event routing, and display

Standalone agents (autonomous, protocol):

  • Manage their own connections (bypass SDK)
  • Domain-agnostic with skill documents providing all context
  • Different execution models (ReAct loop vs YAML protocol)

Display Layer

All agents use the unified PokerWsDisplay class for console output. This handles both "human" (verbose) and "compact" (agent) WebSocket message formats, supporting all message types: welcome, game_state, ack, error, hand_complete, table_status, player_joined, player_left.

Logging

Agents use the shared createJsonlLogger factory for structured logging. Each log entry includes a ts timestamp. Log files are written to logs/ with format: {agent-type}-{identifier}.jsonl.