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

worldsmith

v0.2.0

Published

AI game engine — LLM-powered plan generation, persistent entity state, vector memory

Downloads

29

Readme

Worldsmith

AI game engine library — LLM-powered plan generation, schema-driven entity mutation, persistent state, vector memory.

Your game owns the data, the mutations, and the rendering. Worldsmith owns the LLM orchestration, plan execution, and memory.

Install

npm install worldsmith

Quickstart

Scaffold a new game:

npx worldsmith init

This creates a starter project with a .env.example, prompt templates, and example content.

Or wire it up manually:

import "dotenv/config";
import { createEngine } from "worldsmith";

const engine = createEngine({
  llm: { provider: "openai", model: "gpt-4.1", apiKey: process.env.OPENAI_API_KEY },
});

// Register callbacks — the engine delegates all game logic to you
engine.onMutate((entityId, property, op, value) => {
  // Apply mutation to your entity store
});

engine.onRoll((entityId, stat, difficulty) => {
  // Return { outcome: string, roll: number, margin: number }
  // outcome can be any string: "success", "failure", "critical", "fumble", etc.
});

engine.onGetEntityLocation((entityId) => {
  // Return the location ID for this entity
});

// Handle a player action
const result = await engine.handleAction("player-1", "attack the goblin", ["goblin-3"]);
console.log(result.description);

How It Works

  1. A player performs an action (e.g. "buy the sword", "pick the lock")
  2. The engine sends game context + action to an LLM
  3. The LLM returns a plan — a JSON array of typed step primitives
  4. The engine executes the plan step by step, delegating each step to your registered callbacks
  5. Mutations are batched into transactions; remember/emit steps are deferred until after commit

The 8 Primitives

Every plan the LLM generates is composed of these step types:

| Step | Purpose | Callback | |------|---------|----------| | mutate | Change entity state (set, add, subtract, append, remove). Supports $ref for runtime values. | onMutate | | roll | Stat check with arbitrary named outcome branches | onRoll | | resolve | Delegate to game logic with arbitrary named branches | onResolve | | remember | Store a vector memory for an entity | onRemember | | emit | Push an event to another entity's session | onEmit | | do | Trigger a sub-action by another entity | onDo | | move | Change an entity's location | onMove | | for_each | Iterate over entities from a query, executing inner steps for each | onQueryEntities |

Example plan (LLM output)

{
  "steps": [
    { "step": "roll", "entity": "player-1", "stat": "agility", "difficulty": 12,
      "branches": {
        "success": [
          { "step": "mutate", "entity": "goblin-3", "property": "hp", "op": "subtract", "value": 8 },
          { "step": "emit", "entity": "goblin-3", "event": {
            "type": "attacked", "description": "A swift blade finds its mark.", "actor": "player-1"
          }}
        ],
        "failure": [
          { "step": "emit", "entity": "goblin-3", "event": {
            "type": "attacked", "description": "The goblin sidesteps the clumsy swing.", "actor": "player-1"
          }}
        ]
      }
    },
    { "step": "remember", "entity": "player-1", "text": "Fought the goblin in the east corridor" }
  ],
  "result": {
    "outcome": "depends_on_outcome",
    "outcomes": {
      "success": { "description": "Your blade strikes true." },
      "failure": { "description": "The goblin dodges your attack." }
    }
  }
}

Data flow with $ref

Mutation values can reference resolve/roll results at runtime instead of using LLM-guessed literals:

{
  "step": "resolve", "type": "combat", "config": { "attacker": "player", "defender": "goblin" },
  "branches": {
    "hit": [
      { "step": "mutate", "entity": "goblin", "property": "hp", "op": "subtract",
        "value": { "$ref": "resolve.data.damage" } }
    ]
  }
}

Supported paths: resolve.data.<key>, roll.outcome, roll.roll, roll.margin.

Loops with for_each

Apply steps to multiple entities without enumerating each one:

{
  "step": "for_each",
  "query": { "type": "crew_at_location", "config": { "location": "bridge" } },
  "as": "member",
  "steps": [
    { "step": "mutate", "entity": "{{ member }}", "property": "morale", "op": "subtract", "value": 10 }
  ]
}

The onQueryEntities callback resolves the query to a list of entity IDs. Inner steps execute for each, with {{ member }} replaced.

Configuration

All configuration is done in TypeScript. Secrets come from environment variables via dotenv.

import "dotenv/config";
import { createEngine } from "worldsmith";

const engine = createEngine({
  llm: {
    provider: "openai",                  // openai | groq | chromagolem
    model: "gpt-4.1",
    apiKey: process.env.OPENAI_API_KEY,  // or LLM_API_KEY
    baseUrl: "https://api.openai.com/v1",
    temperature: 0.4,
    maxTokens: 1500,
  },
  prompts: {
    system: "...",                       // inline system prompt content
    systemFile: "./prompts/rules.md",    // or load from file
  },
  memory: {
    path: "./data/memories",             // LanceDB path for entity memories
    ledgerPath: "./data/ledger",         // LanceDB path for world ledger
  },
  plan: { maxSteps: 30 },               // max steps per plan (default: 20)
  dsl: { excludeSteps: ["move"] },       // composable DSL — omit step types, rules, or add custom ops
  allowSelfEmit: false,                  // allow emit steps targeting the acting entity (default: false)
  logger: false,                         // false = silent, omit = console, or pass custom logger
});

Callbacks

Register callbacks to control how the engine interacts with your game:

// Required for mutations
engine.onMutate((entityId, property, op, value) => { });

// Required for roll steps — return any outcome string
engine.onRoll((entityId, stat, difficulty) => {
  return { outcome: "success", roll: 15, margin: 3 };
  // Or multi-outcome: { outcome: "critical", roll: 20, margin: 8 }
});

// Register resolve handlers by type
engine.onResolve("negotiation", async (type, config, context) => {
  return { outcome: "accepted", data: { price: 50 } };
});

// Location changes
engine.onMove((entityId, destination) => {
  return { success: true, newLocationId: "room-2", entityUpdates: {} };
});

// Event delivery to other entities
engine.onEmit((targetEntityId, event) => { });

// Memory storage (deferred, async)
engine.onRemember(async (entityId, text) => { });

// Sub-action delegation
engine.onDo(async (actor, action, targets, parentContext) => {
  return engine.handleAction(actor, action, targets);
});

// Scope validation — throw ScopeError to reject a step
engine.onScopeCheck((step, actorId, actorLocationId) => { });

// Entity location lookup
engine.onGetEntityLocation((entityId) => "room-1");

// Notified when mutations change location-relevant state
engine.onLocationMutate((locationId) => { });

// Detect which mutations are location-relevant
engine.onIsLocationMutation((entityId, property) => property === "locationId");

// Wrap mutation batches in a transaction
engine.onTransaction((fn) => db.transaction(fn));

// Resolve entity queries for for_each loops
engine.onQueryEntities(async (type, config) => {
  if (type === "crew_at_location") return db.getEntitiesAt(config.location as string);
  return [];
});

// Build the user message the LLM sees for each action
engine.onBuildUserMessage(async (entityId, action, targets) => {
  const entity = db.get(entityId);
  return `Action: ${action}\nHP: ${entity.hp}\nInventory: ${entity.inventory.join(", ")}`;
});

// Await memory/ledger initialization before first use
await engine.ready();

Tools

Register tools that the LLM can call during plan generation:

engine.registerTool(
  {
    type: "function",
    function: {
      name: "lookup_price",
      description: "Get the price of an item",
      parameters: {
        type: "object",
        properties: { item: { type: "string" } },
        required: ["item"],
      },
    },
  },
  (args) => JSON.stringify({ price: 25 }),
);

Memory & Ledger

Worldsmith includes a vector memory system backed by LanceDB with OpenAI embeddings.

Memory — per-entity semantic memories with automatic deduplication:

// Store with optional subject and tags
await engine.memory.store("player-1", "Found a secret door behind the waterfall", {
  subject: "exploration",
  tags: ["secret", "waterfall_cave"],
});

// Semantic search — returns { text, score, subject, tags, created_at }
const memories = await engine.memory.search("player-1", "hidden passages", 5);

// Recent memories sorted by time
const recent = await engine.memory.recent("player-1", 10);

Ledger — world-level event log, searchable by semantic similarity:

await engine.ledger.store({ type: "quest", location: "tavern", description: "The barkeep offered a bounty" });
const events = await engine.ledger.search("tavern rumors", 5);

Custom embedding provider — swap out OpenAI embeddings for any provider:

const engine = createEngine({
  embeddingProvider: {
    dimensions: 768,
    embed: async (text) => myEmbeddingApi.embed(text),
    embedBatch: async (texts) => myEmbeddingApi.embedBatch(texts),
  },
});

By default, embeddings use OpenAI text-embedding-3-small via OPENAI_API_KEY. If no provider and no key, memory features are silently disabled.

LLM Providers

| Provider | Config | Notes | |----------|--------|-------| | OpenAI | provider: "openai" | Default. Full tool-calling support. | | Groq | provider: "groq" | OpenAI-compatible API. Fast inference. | | ChromaGolem | provider: "chromagolem" | Custom API format. Requires CHROMAGOLEM_CLIENT_ID. |

Set credentials via llm.apiKey in config or LLM_API_KEY / OPENAI_API_KEY environment variables.

Architecture

Your Game
  │
  ├── .env                (API keys via dotenv)
  ├── prompts/rules.md    (game rules injected into LLM system prompt)
  └── game code           (config, callbacks, entity store, rendering)
        │
        ▼
   ┌──────────────────────────────────────────────┐
   │  Worldsmith Engine                           │
   │                                              │
   │  Orchestrator ──► LLM ──► Plan (JSON)        │
   │       │                      │               │
   │       │              Executor loop            │
   │       │         mutate → roll → resolve →       │
   │       │         remember → emit → do →         │
   │       │         move → for_each                 │
   │       │                      │               │
   │  Memory (LanceDB)    Ledger (LanceDB)        │
   └──────────────────────────────────────────────┘
        │
        ▼
   Game callbacks (you implement these)

Thin core, maximum control. The engine owns plan execution, LLM orchestration, and memory. Your game owns everything else — mutations, scope rules, entity storage, context assembly, and rendering.

License

ISC