{"_id":"@acoyfellow/lab-petri","_rev":"2-9b049bff00a4367582b8aa97d1887bc3","name":"@acoyfellow/lab-petri","dist-tags":{"latest":"0.0.2"},"versions":{"0.0.1":{"name":"@acoyfellow/lab-petri","version":"0.0.1","_id":"@acoyfellow/lab-petri@0.0.1","maintainers":[{"name":"acoyfellow","email":"Coeyman@gmail.com"}],"dist":{"shasum":"6ccbc47a2c586a3a6689792b0d075f96e2cf3e13","tarball":"https://registry.npmjs.org/@acoyfellow/lab-petri/-/lab-petri-0.0.1.tgz","fileCount":25,"integrity":"sha512-/aO9wt2pCtXfH87kEj1eByizDe4VzVAA1vfMoLPi1aNgin5m4MygyklYcmJPFGsEChIxgpL7VUHkeHpY40wIng==","signatures":[{"sig":"MEQCIFKHRR3C/Q2AXreyk/D+9SkXDinnsscwBfFEcE1kgqjMAiA55yg7BOjRhaMO02pFAI1ARpeGcUKMQdoXLknK4dWESg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":69635},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./durable-object":{"types":"./dist/durable-object.d.ts","import":"./dist/durable-object.js"}},"gitHead":"bcb2167e143e6db648a17570fa4e94265856fb2c","scripts":{"build":"tsc","typecheck":"tsc --noEmit"},"_npmUser":{"name":"acoyfellow","email":"Coeyman@gmail.com"},"_npmVersion":"11.6.2","description":"Persistent substrate for Lab experiments - Durable Object backed state with real-time sync","directories":{},"_nodeVersion":"25.2.1","_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.4.0","@cloudflare/workers-types":"^4.20250321.0"},"_npmOperationalInternal":{"tmp":"tmp/lab-petri_0.0.1_1774731048363_0.20823142850252774","host":"s3://npm-registry-packages-npm-production"}},"0.0.2":{"name":"@acoyfellow/lab-petri","version":"0.0.2","description":"Persistent substrate for Lab experiments - Durable Object backed state with real-time sync","type":"module","main":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"import":"./dist/index.js","types":"./dist/index.d.ts"},"./durable-object":{"import":"./dist/durable-object.js","types":"./dist/durable-object.d.ts"}},"scripts":{"build":"tsc","typecheck":"tsc --noEmit"},"devDependencies":{"@cloudflare/workers-types":"^4.20250321.0","typescript":"^5.4.0"},"gitHead":"1c63d547fed232bc89fdce6518b4ee2dbad61e8f","_id":"@acoyfellow/lab-petri@0.0.2","_nodeVersion":"25.2.1","_npmVersion":"11.6.2","dist":{"integrity":"sha512-HUJDbBSl3bcF4pAbx59nINQjsnmuQSeVizxzbo5/2DjYWq67iPJbXs748p2ITzQGQXRSk93Zu/PHfVCsORLskA==","shasum":"857c5ec570ff7362dd0c96fa9f3031fb1e28989b","tarball":"https://registry.npmjs.org/@acoyfellow/lab-petri/-/lab-petri-0.0.2.tgz","fileCount":25,"unpackedSize":69635,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDcOu719b5lRgmcdCjAdPNctgUt3Jgn2A6QDYhr7amryAIgWNcPqtK/1K5Ls0nqTC/mRbDTSWjxYm7eXBiNNbMDoRQ="}]},"_npmUser":{"name":"acoyfellow","email":"Coeyman@gmail.com"},"directories":{},"maintainers":[{"name":"acoyfellow","email":"Coeyman@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/lab-petri_0.0.2_1774731536255_0.699213989899472"},"_hasShrinkwrap":false}},"time":{"created":"2026-03-28T20:50:48.247Z","modified":"2026-03-28T20:58:56.509Z","0.0.1":"2026-03-28T20:50:48.516Z","0.0.2":"2026-03-28T20:58:56.401Z"},"description":"Persistent substrate for Lab experiments - Durable Object backed state with real-time sync","maintainers":[{"name":"acoyfellow","email":"Coeyman@gmail.com"}],"readme":"# lab-petri\n\nPersistent substrate for Lab experiments. Durable Object-backed state with real-time sync.\n\n## The Idea\n\nLab generates code. That code runs in a sandbox. But what if instead of returning values, the code **inhabited** a persistent state?\n\n```\n┌─────────────┐     ┌─────────────┐     ┌─────────────┐\n│   Agent     │────▶│  Sandbox    │────▶│   Petri DO  │\n│  (Lab)      │◄────│  (labPetri) │◄────│  (State)    │\n└─────────────┘     └─────────────┘     └──────┬──────┘\n                                                │\n                                         ┌──────┴──────┐\n                                         │   Browser   │\n                                         │  (Observe)  │\n                                         └─────────────┘\n```\n\nThe agent calls `labPetri.mutate()`. The Durable Object holds state. The UI observes via WebSocket.\n\n## Installation\n\n```bash\nnpm install @acoyfellow/lab-petri\n```\n\n## Quick Start\n\n### 1. Deploy the Durable Object\n\nIn your worker:\n\n```typescript\n// worker/index.ts\nexport { PetriDish } from \"@acoyfellow/lab-petri/durable-object\";\n\n// Route to Petri DO\nif (url.pathname.startsWith(\"/petri/\")) {\n  const dishId = url.pathname.split(\"/\")[2];\n  const id = env.PETRI_DO.idFromName(dishId);\n  const stub = env.PETRI_DO.get(id);\n  return stub.fetch(req);\n}\n```\n\nWrangler/Alchemy config:\n\n```typescript\n// alchemy.run.ts\nconst PETRI_DO = DurableObjectNamespace(\"petri-dish\", {\n  className: \"PetriDish\",\n});\n\nexport const WORKER = await Worker(`${projectName}-worker`, {\n  bindings: {\n    PETRI_DO,\n    // ... other bindings\n  },\n});\n```\n\n### 2. Define Your Experiment\n\n```typescript\nimport type { PetriSchema } from \"@acoyfellow/lab-petri\";\n\ninterface GameState {\n  board: (\"X\" | \"O\" | null)[];\n  turn: \"X\" | \"O\";\n  winner: string | null;\n}\n\nconst gameSchema: PetriSchema<GameState> = {\n  id: \"tic-tac-toe-v1\",\n  mutations: [\"placeX\", \"placeO\", \"reset\", \"log\"],\n  \n  seed: () => ({\n    board: Array(9).fill(null),\n    turn: \"X\",\n    winner: null,\n  }),\n  \n  reduce: (state, mutations) => {\n    let newState = { ...state };\n    \n    for (const m of mutations) {\n      switch (m.op) {\n        case \"placeX\":\n          newState.board[m.index] = \"X\";\n          newState.turn = \"O\";\n          break;\n        case \"placeO\":\n          newState.board[m.index] = \"O\";\n          newState.turn = \"X\";\n          break;\n        case \"reset\":\n          return gameSchema.seed();\n      }\n    }\n    \n    return newState;\n  },\n};\n```\n\n### 3. Connect from Browser\n\n```svelte\n<script>\nimport { PetriClient } from \"@acoyfellow/lab-petri\";\n\nconst client = new PetriClient<GameState>({\n  wsUrl: \"wss://lab.coey.dev/petri/game-1\",\n  apiUrl: \"https://lab.coey.dev/petri/game-1\",\n  dishId: \"game-1\",\n});\n\n// Subscribe to real-time updates\nclient.subscribe((snapshot) => {\n  renderBoard(snapshot.state);\n});\n\n// Handle user moves\nfunction handleClick(index) {\n  client.mutate([\n    { op: \"placeX\", index },\n    { op: \"log\", message: `X placed at ${index}` },\n  ]);\n}\n</script>\n```\n\n### 4. Agents Inhabit the Dish\n\n```typescript\nimport { runGenerate } from \"@acoyfellow/lab\";\n\nconst result = await runGenerate({\n  prompt: `Play tic-tac-toe. Use labPetri to make moves.`,\n  capabilities: [\"petri\"], // Enables labPetri binding\n  mode: \"code\",\n  input: {\n    dishId: \"game-1\", // Required for labPetri\n    // ... other context\n  },\n});\n\n// Generated code looks like:\n// const state = await labPetri.getState();\n// await labPetri.mutate([\n//   { op: \"placeO\", index: findBestMove(state) },\n//   { op: \"log\", message: \"O takes center\" }\n// ]);\n```\n\n## API Reference\n\n### PetriSchema\n\n```typescript\ninterface PetriSchema<TState> {\n  /** Unique schema identifier */\n  id: string;\n  \n  /** Allowed mutation operations */\n  mutations: string[];\n  \n  /** Factory for initial state */\n  seed: () => TState;\n  \n  /** Apply mutations to state (must be pure) */\n  reduce: (state: TState, mutations: Mutation[]) => TState;\n}\n```\n\n### PetriClient\n\n```typescript\nclass PetriClient<TState> {\n  constructor(options: {\n    wsUrl: string;      // WebSocket URL\n    apiUrl: string;     // HTTP API URL\n    dishId: string;     // Unique dish identifier\n    reconnectDelay?: number;\n  });\n  \n  /** Connect via WebSocket */\n  connect(): void;\n  \n  /** Disconnect */\n  disconnect(): void;\n  \n  /** Subscribe to state changes */\n  subscribe(observer: (snapshot: PetriSnapshot<TState>) => void): () => void;\n  \n  /** Apply mutations */\n  mutate(mutations: Mutation[]): Promise<PetriSnapshot<TState>>;\n  \n  /** Get current snapshot (async) */\n  getSnapshot(): Promise<PetriSnapshot<TState>>;\n}\n```\n\n### labPetri (in sandbox)\n\nWhen `capabilities: ['petri']` is specified:\n\n```typescript\n// Inside generated code:\nconst state = await labPetri.getState();\n// Returns the current state from the DO\n\nawait labPetri.mutate([\n  { op: string, ...params },\n  { op: \"log\", message: string }\n]);\n// Applies mutations, returns new snapshot\n```\n\n## Examples\n\n- **Garden** - Plants growing, seasons changing, agents tending ([examples/garden.ts](./examples/garden.ts))\n- Tic-tac-toe - Agents playing against humans\n- Conway's Game of Life - Cellular automata\n\n## Architecture\n\nSee [ARCHITECTURE.md](./ARCHITECTURE.md) for detailed design decisions and data flow.\n\n## Differences from Traditional Lab\n\n| Traditional Lab | lab-petri |\n|----------------|-----------|\n| Agent returns value | Agent calls methods |\n| State is ephemeral | State is persistent |\n| One-shot execution | Continuous observation |\n| Host applies changes | DO validates & applies |\n| No real-time updates | WebSocket broadcasts |\n\n## License\n\nMIT\n","readmeFilename":"README.md"}