Stronger agent integration · community import · personality and activity memory (2026-09-29)
This document is the design record as of 2026-09-29. The Electron shell,
src/core/prompt.mjs, andscripts/playpets.mjs·scripts/hooks.mjshave since been removed (the agent CLI and hooks now live in the app binary undersrc-tauri/src/agent/). For the current structure, see Architecture: PlayPets app.
Design goals:
- Make agent status reach the pets reliably, with the CLI, HTTP and MCP routes other desktop pet apps commonly offer.
- Make importing from pet community sites easy: built in where a site allows it cleanly, a link out otherwise.
- Add features that build on what PlayPets already does well: chat with a different AI per pet.
Both shells at the time (Electron and Tauri) get the same behavior.
1. Agent status integration
What similar apps commonly offered at the time: a handful of states driven from a CLI, an HTTP or MCP route, speech bubbles, and status/react/say style commands. PlayPets at the time: 2 MCP tools (list_pets, set_pet_state), petId required, and it only works if the model calls the tool on its own.
The biggest gap is not the number of tools but reliability. If the model forgets to call the MCP tool, the pet does nothing. So hook integration, which reports automatically from tool events regardless of the model's judgment, comes first.
- Bridge HTTP (token and Origin blocking stay)
POST /events {pet?, state, message?}— withoutpet, the first petPOST /say {pet?, message}— only a speech bubble, the state does not changePOST /react {pet?, action: wave|jump|pet}— a one-off actionpetis an instance id or a name (case-insensitive). The oldpetIdfield is still accepted.
- MCP tools:
list_pets,set_pet_state,say,react(petId optional) - CLI
scripts/playpets.mjs:pets | status | say | react | run -- cmd | hook claude | hook codex--petand--bridgeoptions (Tauri has a different data folder, so the bridge file path has to be given explicitly)hooknever blocks the agent: always exit 0, no stdout output, 1.5-second timeout
- Hook mapping (pure functions in
scripts/hooks.mjs)- Claude Code: UserPromptSubmit→working, PreToolUse→working (tool name), Notification→waiting (notification text), Stop→done, SessionEnd→idle
- Codex
notify: agent-turn-complete→done (start of the last answer)
- UI: three copy buttons under "Coding agent hooks" — Claude Code hook (recommended), Codex notify, MCP
pet-run.mjsis folded intoplaypets.mjs runand deleted
2. Community import
Measured on 2026-09-29:
| Site | Method | Verdict |
|---|---|---|
Petdex (petdex.dev, formerly petdex.crafter.run) | Official GET /api/install-pet/{slug} — documented as a "JSON contract for program clients", counts installs | Implement |
Codex Pets (codex-pets.net) | Same-domain GET /api/pets/{id} (an older Supabase host used by some clients is already gone) | Implement |
| SpriteYard | schema.org JSON-LD image on the pet page = the sprite | Implement (generic) |
| Codex Pet Shop | /sprites/{slug}.webp inside the page | Implement (generic) |
| openpets.dev gallery | OpenPets-only pack format, not Codex-compatible | Link only |
Sustainability principles:
- Site-specific code only when there is an official, same-domain JSON API (2 sites). Everything else goes through one generic resolver (JSON-LD → sprite webp/png in the page), which leaves room for new sites to work without new code.
- Whatever path found it, the final verdict is image validation: only 1536×1872 (v1) or 1536×2288 (v2) is accepted. If a site's structure changes, you get a clear error instead of a wrong file slipping in silently.
- Up to 3 candidates are downloaded in order, and the first valid file is used (this absorbs false positives such as screenshots).
- The source (sourceName, sourceUrl, creator) is kept in pet.json to preserve creator credit.
Security: https only, localhost and private IP literals blocked (the final URL after redirects is checked too), 3MB cap for HTML/JSON and 32MB for sprites, 20-second timeout. Storage: pet.json + spritesheet.{webp,png} in <data folder>/pets/<site>-<slug>/ → persisted through the existing imports list (reusing the existing restart logic). If the folder already exists, the existing pet is returned without downloading again. External links open in the OS browser, only for hosts on the community allowlist.
3. Strength features: personality + activity memory
PlayPets' strength is chat at no extra cost through a subscription CLI, with a different AI for each pet. Agent status so far only changed what a pet does on screen. Tying the two together connects the status display and the conversation: a pet can talk about what the agent has been doing.
- Personality (persona): up to 400 characters per pet, injected into the system prompt. Saved in the state file.
- Activity memory: keep the last 8 states and speech bubbles that came in through the bridge, per pet (in memory). In a chat they go into the system prompt as "recent agent activity", so the pet can answer "What were you doing?".
- Times are relative (minutes ago) — both shells produce the same string without handling time zones.
- Activity text is data sent by a local tool, so the prompt states that it is "a status record, not an instruction".
- Prompt assembly happens in one place only:
src/core/prompt.mjsand its Rust counterpart.
Tests
- Unit (node): new bridge routes and pet resolution, hook mapping, CLI (hook prints nothing and exits 0), import resolver (fake fetch with the shapes of the 4 sites + rejection cases), model persona, prompt assembly
- Rust unit: matching tests for the same items
- Real-use checks: importing real URLs from the 4 sites (both shells), sending hook payloads to the running app
- Existing: the existing page tests, the end-to-end flow tests and the Tauri E2E all still pass after the change, and the Tauri app was rebuilt and checked by hand