Skip to content

fix(avatars): bundle a default avatar with each first-run template (#2693) - #2776

Open
obasilakis wants to merge 2 commits into
devfrom
fix/2693-bundled-default-avatars
Open

fix(avatars): bundle a default avatar with each first-run template (#2693)#2776
obasilakis wants to merge 2 commits into
devfrom
fix/2693-bundled-default-avatars

Conversation

@obasilakis

Copy link
Copy Markdown
Contributor

Summary

Closes #2693.

A fresh install seeds scout / sage / scribe with only an avatar_prompt, and generation needs GEMINI_API_KEY, which no new instance has — so the default fleet renders as initials.

Decision taken on the open question in the issue (bundled generated portraits vs hand-drawn art vs code placeholder): the ticket's preferred mechanism — a static image bundled with each shipped template — with hand-drawn schematic art as the image. No Gemini key was available to render the prompts, and the schematic family is the one already approved for first-run illustrations (ent#581).

  • A local: template may ship avatar.webp / avatar.png (≤2 MB, ≤4096×4096) beside template.yaml.
  • At create, when the template also declares avatar_prompt, crud._install_bundled_avatar re-encodes it through optimize_avatar into /data/avatars/{name}.webp, then seeds the prompt as a default avatar (is_default_avatar=1). Re-encoding matters: the deploy-local root holds user-uploaded templates and the avatar route is unauthenticated.
  • Generate Default Avatars still overwrites it once a key exists (AC3). No outbound call (AC1/AC5).
  • A missing, oversized or undecodable image degrades to the old prompt-only seed — never fails the create.
  • scribe gains the avatar_prompt it lacked (AC4); image is only installed alongside a prompt, so every bundled face stays regenerable and the agent-detail avatar_url (which requires a prompt) is populated.

Tests

tests/unit/test_1484_create_agent_characterization.py:

  • test_2693_bundled_avatar_installed_as_default — WebP lands in the store, row written as default with the template prompt
  • test_2693_corrupt_bundled_avatar_keeps_prompt_seed — bad image: no file, prompt seed still happens
  • test_2693_first_run_fleet_ships_bundled_avatars — every agent in default-system.yaml has an avatar_prompt and a decodable avatar.webp

Both behaviour tests fail with the crud.py change stashed; full file 45 passed. Template/manifest/gitignore suites (438) pass.

Not run: live create on a real stack.

Not covered

  • cornelius is seeded separately from github:Abilityai/cornelius, not from the bundled manifest, so it still shows initials until a key exists. Covering it means reading a bundled image from a GitHub template — a follow-up if wanted.
  • startup.sh copies the whole template dir into the workspace, so avatar.webp (~10 KB) also lands in /home/developer. Harmless; not filtered.

🤖 Generated with Claude Code

https://claude.ai/code/session_01VKzc3MG4Fa1sJ1KuMmBtcV

…2693)

A fresh install seeds scout/sage/scribe with only an avatar_prompt, and
generation needs GEMINI_API_KEY, which no new instance has — so the first
screen an operator sees is a fleet of initials.

A local: template may now ship avatar.webp/avatar.png beside template.yaml.
At create, when the template also declares avatar_prompt, the image is
re-encoded through optimize_avatar into /data/avatars before the prompt is
seeded as a DEFAULT avatar, so Generate Default Avatars still overwrites it
once a key exists. Offline, no outbound call. A missing, oversized or
undecodable image degrades to the old prompt-only seed.

The three starter templates ship hand-drawn schematic glyphs (no Gemini key
was available to render the prompts, and the schematic family matches the
approved first-run illustrations). scribe gains the avatar_prompt it lacked.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VKzc3MG4Fa1sJ1KuMmBtcV
template_dir = _resolve_local_template_dir(config.template[6:])
for filename in _BUNDLED_AVATAR_NAMES:
source = template_dir / filename
if not source.is_file() or source.stat().st_size > _BUNDLED_AVATAR_MAX_BYTES:
template_dir = _resolve_local_template_dir(config.template[6:])
for filename in _BUNDLED_AVATAR_NAMES:
source = template_dir / filename
if not source.is_file() or source.stat().st_size > _BUNDLED_AVATAR_MAX_BYTES:
from PIL import Image
from utils.image_optimize import optimize_avatar

data = source.read_bytes()
if img.width * img.height > 4096 * 4096:
raise ValueError(f"bundled avatar is {img.width}x{img.height}, max 4096x4096")
_AVATAR_DIR.mkdir(parents=True, exist_ok=True)
(_AVATAR_DIR / f"{config.name}.webp").write_bytes(optimize_avatar(data))
…it (#2693)

CodeQL raised four py/path-injection alerts on _install_bundled_avatar: the
template dir and agent name are already contained by
_resolve_local_template_dir and name validation, but the analyzer cannot
follow those callees. Normalize and prefix-check the final source and
destination paths inline, the same barrier routers/avatar.py uses.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VKzc3MG4Fa1sJ1KuMmBtcV
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants