Context
SkillDev toolsRuntime context passed to compiled closures — the world as seen from inside
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Context skill
What this skill tells your AI
The instructions your AI receives, as published by simhacker/moollm in skills/context/SKILL.md and read by ahel’s review.
"The context IS the world as seen from inside the closure." — Dave Ungar, on lexical scope
What Is It?
The world object is passed to every compiled closure. It provides:
- Standard keys — Always present (adventure, player, room, turn)
- Extended keys — Contextual (object, target, npc)
- Skill namespaces — Skills register state under
world.skills.skill_name - Utility functions — API for interacting with the world
Why "world" not "ctx"?
- More evocative — closures see the WORLD
- Self-documenting —
world.player,world.room - Matches the mental model
Standard Keys
Always present in every world:
world.turn // Current simulation turn
world.timestamp // ISO timestamp
world.adventure // Root adventure state
.name
.flags // Global boolean flags
.world_state // Global key/value state
world.player // Current player
.id
.name
.location // Path to current room
.inventory // Array of item ids
.buffs // Active buffs
world.room // Current room
.id
.name
.path
.exits
.objects
.is_dark
.is_dangerous
world.party // Party state
.members
.leader
Extended Keys
Present when relevant:
// When running object simulate/methods:
world.object // The object being simulated
.id
.state // Object's mutable state
// Methods are bound: world.consume_fuel(1)
// When action targets something:
world.target // The target
.id
.type // "object", "character", "room"
// When NPC is simulating:
world.npc // The NPC
.id
.goals
.state
Skill State Namespaces
Skills register state under world.skills.<skill_name> using underscores:
// Skill "economy" → world.skills.economy
world.skills.economy.gold // 100
// Skill "pie-menu" → world.skills.pie_menu (underscore!)
world.skills.pie_menu.last_selection // "north"
// Skill "time" → world.skills.time
world.skills.time.hour // 14
world.skills.time.phase // "afternoon"
Why underscores? Dashes aren't valid JS/Python identifiers. foo-bar skill → foo_bar namespace.
This keeps skill state organized and avoids collisions.
Utility Functions
Methods bound to world for interaction:
Narrative
world.emit("The lamp dies!") // Show message
world.narrate("Darkness falls.", "dramatic")
Events
world.trigger_event("GRUE_APPROACHES", { room: world.room.path })
Inventory
world.has("brass-key") // true/false
world.give("gold-coins") // Add to inventory
world.take("used-potion") // Remove from inventory
Flags
world.flag("dragon_slain") // Get flag
world.set_flag("treasure_found", true) // Set flag
State
world.get("object.state.fuel") // Get by path
world.set("object.state.lit", true) // Set by path
Navigation
world.go("../maze/room-a/") // Move player
world.can_go("north") // Check exit
Buffs
world.add_buff({ name: "Caffeinated", effect: { energy: +2 }, duration: 5 })
world.remove_buff("caffeinated")
world.has_buff("grue_immunity")
Logging
world.log("Debug: fuel = " + world.object.state.fuel)
Example: Lamp Simulate
simulate_js: (world) => {
if (world.object.state.lit) {
world.consume_fuel(1); // Call object method
if (world.object.state.fuel <= 0) {
world.extinguish(); // Call object method
world.emit("The lamp sputters and dies!");
if (world.room.is_dark && world.room.is_dangerous) {
world.trigger_event("GRUE_APPROACHES");
}
}
}
}
Example: Guard Expression
guard: "player has the key AND room is not dark"
guard_js: (world) => world.has("brass-key") && !world.room.is_dark
Example: Score Calculation
score_if: "player is tired OR room is dark"
score_if_js: (world) => world.has_buff("tired") || world.room.is_dark
Example: Skill State
# Skill "economy" needs to check gold
guard: "player has at least 10 gold"
guard_js: (world) => world.skills.economy.gold >= 10
# Skill "pie-menu" checks last selection
score_if: "last pie menu selection was north"
score_if_js: (world) => world.skills.pie_menu.last_selection === "north"
Design Principles
Structured, Not Arbitrary
world is NOT just a bag of key/values. It has defined structure:
- Standard keys are always present
- Extended keys appear in context
- Skills namespace their state (with underscores!)
- Functions are bound methods
Skill Namespaces (Underscores!)
Skills don't pollute root world. They register under world.skills.skill_name:
// Skill "economy" → world.skills.economy
world.skills.economy.gold
world.skills.economy.currency
// Skill "pie-menu" → world.skills.pie_menu (underscore!)
world.skills.pie_menu.last_selection
world.skills.pie_menu.hover_direction
// Skill "foo-bar" → world.skills.foo_bar
world.skills.foo_bar.some_state
Rule: skill-name with dashes → skill_name with underscores in namespace.
Methods Are Bound
Object methods appear as functions on world:
// Object defines:
methods:
consume_fuel: "reduce fuel by amount"
// At runtime, method is bound:
world.consume_fuel(1) // Works!
Related Skills
- object — Provides ctx.object
- room — Provides ctx.room
- adventure — Provides ctx.adventure
- buff — Used by ctx.add_buff/has_buff
Dual Runtime: Python + JavaScript
CRITICAL: We always generate BOTH _js AND _py versions of compiled expressions.
# Natural language
guard: "player has the key AND room is not dark"
# BOTH generated:
guard_js: (world) => world.has("brass-key") && !world.room.is_dark
guard_py: lambda world: world.has("brass-key") and not world.room.is_dark
Why Dual Runtimes?
| Runtime | Purpose |
|---|---|
| Python | Server-side simulation, testing, LLM tethering |
| JavaScript | Browser runtime, standalone play |
Keeping Them In Sync
- Same semantics — Both should produce identical results
- Same world structure —
world.player,world.room, etc. - Same utility functions —
world.has(),world.emit(), etc. - Generated together — LLM produces both in one pass
The Compilation Event
- event: COMPILE_EXPRESSION
field: guard
source: "player has the key"
targets:
- field: guard_js
language: javascript
- field: guard_py
language: python
expected_type: boolean
Python Runtime Class
class World:
"""Python runtime context — mirrors JavaScript World class."""
def __init__(self, adventure_data):
self.turn = 0
self.adventure = adventure_data
self.player = adventure_data['player']
self.room = None # Set on navigation
self.party = adventure_data['party']
self.object = None # Set during object simulation
self.skills = {} # Skill state namespaces
def has(self, item_id: str) -> bool:
return item_id in self.player.get('inventory', [])
def flag(self, name: str) -> bool:
return self.adventure.get('flags', {}).get(name, False)
def emit(self, message: str):
print(message) # Or queue for output
def trigger_event(self, name: str, data=None):
# Event system handles this
pass
JavaScript Runtime Class
class World {
/** JavaScript runtime context — mirrors Python World class. */
constructor(adventureData) {
this.turn = 0;
this.adventure = adventureData;
this.player = adventureData.player;
this.room = null; // Set on navigation
this.party = adventureData.party;
this.object = null; // Set during object simulation
this.skills = {}; // Skill state namespaces
}
has(itemId) {
return (this.player.inventory || []).includes(itemId);
}
flag(name) {
return (this.adventure.flags || {})[name] || false;
}
emit(message) {
console.log(message); // Or queue for UI
}
triggerEvent(name, data) {
// Event system handles this
}
}
Protocol Symbol
RUNTIME-CONTEXT — The world passed to closures (Python + JavaScript)
Signals
- GitHub stars
- 52
- Forks
- 5
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
context-simhacker- Source
- github.com/simhacker/moollm