Skip to content

[Work in progress]: Palette as a skill - #2

Draft
anupamamurthi wants to merge 25 commits into
mainfrom
palette_skill
Draft

[Work in progress]: Palette as a skill#2
anupamamurthi wants to merge 25 commits into
mainfrom
palette_skill

Conversation

@anupamamurthi

Copy link
Copy Markdown
Collaborator

Goes hand in hand with - cuga-project/cuga-agent#551

@anupamamurthi
anupamamurthi requested a review from praveenv July 28, 2026 01:46
anupamamurthi and others added 24 commits July 27, 2026 22:32
…ract

Three fixes found by installing the skill into Claude Code for the first
time and driving a deck through it end to end.

install() left the checkout rendered for whichever host it installed. A
`--host claude-code` install therefore made `make skill-check` fail for no
visible reason, and committing the result would have shipped Bash-flavoured
instructions to every CUGA user. check() already snapshotted payload/ for
exactly this reason; install did not. Same fix, plus tests that the
checkout is restored and the installed copy still carries its own host.

run_deck's `next` and `dir` were attached to the stage transitions -- each
seen once -- and missed on the two polling returns, which are the long
middle of a build and precisely where an agent drifts into narrating
instead of calling again. Every non-terminal result now carries both.

Adds docs/skill-handbook.html: build, release, update, consume and verify
on one page, with the failure modes that shaped the design. It links to
GitHub by branch, so tests assert every link resolves to a file in this
repo and that none points at main.

Verified on both hosts: CUGA via the demo preset, Claude Code by
installing to ~/.claude/skills and building a 9-slide deck from the
vendored wheel -- verified: true, IBM Plex present in the OOXML.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two gaps the Claude Code run exposed.

Nothing documented how to test on a host other than CUGA. TESTING.md and the
cheatsheet now cover it: install from the checkout (running the installer from
an installed package gives an empty vendor/), what the four tells are that the
right variant landed, and the fact that installing for one host no longer
disturbs another.

Nothing said which Palette a deck talked to. The client resolves $PALETTE_URL
then contract.DEFAULT_BASE_URL, which is http://127.0.0.1:18814 -- local. A
deployed instance is the intended default and contract.py says so, but no URL
is pinned there yet. Rather than asking anyone to reason about it, the docs
point at the server log: the thread id from .palette-deck.json either appears
there or the deck was served elsewhere.

The handbook gains both, plus the install-leak bug in its failure table.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
docs/skill-handbook.html was the fragment the Artifact publisher takes --
no doctype, no charset, no viewport, because the publisher supplies those.
Served from a static host it renders in quirks mode, turns every arrow and
em-dash into mojibake, and hands a phone a desktop-width page.

So the fragment moves to docs/skill-handbook.body.html and stays the single
source; scripts/build-handbook.py wraps it into the servable page. One copy
of the prose, two outputs, and `make handbook-check` plus a test fail if
they diverge -- the same contract as `make skill-check`. The pre-commit hook
catches the likely mistake, which is editing the fragment and committing
only that.

Self-contained, so `python -m http.server -d docs` or any static host works
with no build step: verified serving at 200, text/html, 49 KB.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The cheatsheet assumed a working checkout and never mentioned Node,
LibreOffice, Poppler or the RITS key, so someone sent straight to it on a
fresh machine would reach `serve ensure` before discovering what was
missing. `serve doctor` reports it, but only if you know to run it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Four docs told you to run

    grep -E "draft_async|build_async" ~/.local/state/palette/server.log

That reports nothing for a deck that rendered perfectly well. app.py
installs a FileHandler per session, so build events land in
workspace/<tid>/session.log; server.log only ever gets startup and
shutdown. Demonstrated on a real deck: session.log holds both stages,
server.log holds zero mentions of the thread.

A verification step that lies is worse than none, and this one was
load-bearing in the cheatsheet, the guide, the testing ladder and the
handbook -- all of which spend paragraphs telling you to believe the files
over the agent's summary.

Corrected everywhere, with a test that fails any doc grepping server.log
for build events, and a note on why so it does not come back.

Also unbuffers the server's stdout (-u plus PYTHONUNBUFFERED). Stdout is a
file in every mode, so Python block-buffers it and the log can sit an 8 KB
buffer behind the server. That was not the cause here, but it is a real
way for the same class of confusion to happen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Palette is driven as a CLI now (build-plan / edit-plan / build-deck), so the
HTTP-era skill was the wrong abstraction end to end: a contract module, an
httpx client, per-host generated regions, a wheel to vendor, an installer and
a release pipeline -- all to reach a server the skill no longer talks to.

The skill is now what skills.sh installs look like: a folder with SKILL.md and
one script, no package, nothing generated.

    skills/palette/
    |- SKILL.md          what the agent is told
    `- scripts/deck.py   starts a long build detached

deck.py exists for one reason. `build-deck` blocks for three to ten minutes and
CUGA kills a sandbox step at 120s, so a direct call is killed part-way while
the render keeps going -- the work completes and nobody collects it. `start`
returns in ~50ms; `status` polls. Claude Code's Bash allows ten minutes, so it
calls build-deck directly; the skill documents both and says which is which.

`status` reports done only when deck.pptx is on disk and over 20KB, never from
an exit code. Verified: a 5KB stub reports error, a missing build refuses
rather than guessing, and a failed build surfaces the log tail.

Removed with the old design: contract.py, client.py, build_skill.py, hosts.py,
install.py, release.py, payload/, four test modules and the doc set built
around them. service.py survives -- it supervises the web UI's server, which
still exists -- and no longer imports the deleted client. docs/skill-handbook
went too: it described the HTTP architecture in detail and would have been
confidently wrong.

tests/test_skill.py replaces all of it by reading palette.py's argparse setup
and failing if the skill names a command or flag that does not exist.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A build that cannot reach the models retries every stage before failing --
measured at 51 minutes, with nothing but connection timeouts in the log. To a
poller that is indistinguishable from a slow render, so the agent keeps
reporting "still building" and the user waits.

status already returns elapsed_seconds; the skill now says to speak up past
about fifteen minutes and points at build.log, where the reason has been
sitting the whole time.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Found by the first real run once RITS came back.

The agent ran `skills/palette/palette.py` -- it lives in the checkout, not the
skill folder, and the instruction asked it to hold both roots plus a cd in its
head. deck.py now fronts plan/edit/start/status, resolves paths against the
caller's cwd and runs palette.py from the checkout itself, so nobody has to
keep them straight. SKILL.md says outright: do not run palette.py yourself.

Two reported numbers were wrong. `progress` was always empty because
build-deck prints only when it finishes -- its stage logging goes to a
per-session file -- so the skill told the agent to relay a line it would never
have; running builds now report elapsed time and a note. And `elapsed_seconds`
measured wall clock since start, so polling a finished deck an hour later
reported an hour, which would have tripped the fifteen-minute stall warning on
a build that took nine minutes. It now measures duration.

Verified end to end: plan 25s, edit 37s (applied the change), build ~8 min ->
verified: true, 4 slides, IBM Plex in the OOXML.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

1 participant