Skip to content

Studio Code: Give the agent its WordPress.com site's plan features instead of describing plan tiers - #5170

Merged
chubes4 merged 1 commit into
trunkfrom
studio-code/wpcom-plan-facts
Oct 17, 2026
Merged

chubes4 merged 1 commit into
trunkfrom
studio-code/wpcom-plan-facts

Conversation

@chubes4

@chubes4 chubes4 commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Related issues

How AI was used in this PR

Claude (via the Pi coding agent in a Roadie session) wrote the change and its test, and ran the read-only check against live WordPress.com sites below. Opened as a draft for human review.

Proposed Changes

For WordPress.com sites, the system prompt says the edit-manage-remote-site skill "starts with a plan check". The skill then has the agent read plan.product_slug and decide what the plan allows from a description of the tiers: "Free plans allow content only…; Paid plans progressively add custom CSS, global styles, plugin management…". That description lives in Studio and drifts from what WordPress.com actually sells.

WordPress.com already says what a site's plan includes: GET /sites/{id} returns plan.features.active, slugs such as global-styles, custom-design and install-plugins. The agent never saw them, because wpcom_request deleted the whole plan.features object to keep the result small. Nearly all of that object is features.available, the catalogue of what other plans would add (about 65K characters). features.active is 12 slugs on a Free site and 76 on Premium.

  • wpcom_request keeps plan.features.active and still strips features.available.
  • The skill's plan section becomes: plan.features.active lists what the site's plan includes; a change needing a feature it lacks needs an upgrade (load hosting-plans-helper for which plan adds it); inline styles and style attributes are no way around it. The tier descriptions go.
  • System prompt: the rule drops "it starts with a plan check, and such sites change only through wpcom_request, never WP-CLI, Bash, or local files". The skill still says the latter, and a WordPress.com site has no local path those routes could reach.

Studio holds no plan knowledge after this; WordPress.com stays the source of truth.

Testing Instructions

  • Unit test: npx vitest run apps/cli/ai/tests/wpcom-request.test.ts. A GET / result keeps plan.features.active and drops features.available.
  • Live, read-only: GET / through wpcom_request for two of my sites.
    • Free site: 5,625-character result, 12 active features, no custom-design.
    • Premium site: 6,840-character result, 76 active features, including custom-design.
  • By hand: studio code, pick a Free WordPress.com site with /site, and ask for a global style change. The agent reads the plan's features and says the change needs an upgrade.

Pre-merge Checklist

  • Have you checked for TypeScript, React or other console errors?

…stead of describing plan tiers

wpcom_request stripped the whole plan.features object from GET /, so the
edit-manage-remote-site skill described from memory what each plan tier allows. It
now keeps plan.features.active, the features this site's plan includes, and strips only
features.available, the 65K catalogue of what other plans would add. The tier prose,
and the plan check in the system prompt rule, go.
@chubes4 chubes4 changed the title Studio Code: State a WordPress.com site's plan and role, and enforce them in wpcom_request instead of the prompt Studio Code: Give the agent its WordPress.com site's plan features instead of describing plan tiers Oct 17, 2026
@chubes4
chubes4 force-pushed the studio-code/wpcom-plan-facts branch from c25b686 to 7db0a89 Compare October 17, 2026 13:36
@chubes4
chubes4 marked this pull request as ready for review October 17, 2026 13:54
@wpmobilebot

Copy link
Copy Markdown
Collaborator

📊 Performance Test Results

Comparing 7db0a89 vs trunk

app-size

Metric trunk 7db0a89 Diff Change
App Size (Mac) 930.76 MB 930.75 MB 0.00 MB ⚪ 0.0%

site-editor

Metric trunk 7db0a89 Diff Change
load 1161 ms 1164 ms +3 ms ⚪ 0.0%

site-startup

Metric trunk 7db0a89 Diff Change
siteCreation 8597 ms 8574 ms 23 ms ⚪ 0.0%
siteStartup 5902 ms 5904 ms +2 ms ⚪ 0.0%

Results are median values from multiple test runs.

Legend: 🟢 Improvement (faster) | 🔴 Regression (slower) | ⚪ No change (<50ms diff)

@chubes4
chubes4 merged commit 77c5454 into trunk Oct 17, 2026
13 checks passed
@chubes4
chubes4 deleted the studio-code/wpcom-plan-facts branch October 17, 2026 14:26
chubes4 added a commit that referenced this pull request Oct 20, 2026
…epending it to every message (#5192)

## Related issues

- Follows #5170, #5189, #5190: moving Studio Code context from prose
into structure, one small step at a time.
- First step toward composing site facts the way the system prompt
already composes tool snippets: each fact is rendered by the code that
owns it, from Studio's own data.

## How AI was used

Claude (via the Pi coding agent in a Roadie session) traced how the
active site reaches the model, wrote the change and tests, and ran the
eval below. I reviewed the diff and the eval traces.

## Proposed Changes

The active site was stated by prepending a `[Active site: …]` line to
every user message. So the same fact piled up in the conversation
history once per turn, and it was mixed into what the user typed.

This PR states it once, in an `## Active site` section of the system
prompt instead:

- `buildSystemPrompt` takes `activeSite` and renders the section. The
text is the same as before, minus the brackets: name, path,
running/stopped, and the DESIGN.md path when there is one; or ID, URL
and "WordPress.com" for a remote site.
- The Pi runtime already rebuilds the session, and so the system prompt,
on every turn. It now passes the `activeSite` it was already receiving
into `buildSystemPrompt`, so the section always reflects the current
site and running state, including after compaction.
- `ai` and the eval runner stop prepending the line. The user message is
exactly what the user typed.
- The three skills and the one prompt line that referred to "the
active-site line" now say "the Active site section".

Trade-off: when the active site or its running state changes, the system
prompt changes, so that one turn misses the prompt cache. Before, each
turn added the line to the history instead.

## Testing Instructions

Unit tests: `npx vitest run apps/cli/ai apps/cli/commands/ai` (245
pass).
- `system-prompt.test.ts`: the section for a local site, a WordPress.com
site, and a site with a DESIGN.md. It sits above the user's global
instructions and is absent without an active site.
- `commands/ai/tests/index.test.ts`: the dispatched message is the typed
text, and the runtime gets the site with the daemon's running state.

Eval: four cases that depend on the agent knowing its active site, 2
runs each, this branch against trunk (`76f5c92bf`), using the `fast`
model:

| Case | What passes | Branch | Trunk |
|---|---|---|---|
| Tagline | Sets `blogdescription` and `blog_public` on the active site
| 2/2 | 2/2 |
| Plugins | Lists every installed plugin of the active site | 2/2 | 2/2
|
| Admin bar (core file) | Core checksums still verify, and the change
lives in `wp-content/` | 1/2 | 1/2 |
| Design system | Reads the seeded DESIGN.md and the new page uses its
required CTA text | 2/2 | 2/2 |

Neither admin-bar failure involves the active site:
- On the branch, the agent left core alone and set the height through
Additional CSS (`wp_update_custom_css_post`). That's valid, but the
check only looks at files.
- On trunk, the model emitted a malformed tool call and the turn ended.

In all 16 runs the agent worked in the correct site directory. The
result is parity: the same facts, no longer repeated in the history.

Multi-message eval, using #5193's token usage and `followUps`: a seeded
site and eight messages (tagline, theme, pages, create a draft page,
plugins, permalinks, site title), 2 runs per arm. To match the app, the
trunk arm adds the site line to every message.

| | Trunk (line in every message) | This PR (system-prompt section) |
|---|---|---|
| Final state correct (tagline, title, draft page) | 2/2 | 2/2 |
| Site lines saved in the session | 8 per run | 0 |
| Copies of the line re-sent across the run's requests | 73–78 | 0 (the
section is sent once per request) |
| Total tokens per run | 218k, 244k | 225k, 214k |

The line is about 25 tokens, so this saves roughly 1.5k tokens per
eight-message run, under 1% and well inside the run-to-run noise. The
gain is in the transcript (it holds what the user typed, and no outdated
copies of the site's state), not in the token bill.

Manual:
1. `studio ai` with a site selected, then ask "what site are you working
on?" The agent answers from the system prompt, and your message in the
history has no prefix.
2. Switch site with `/site`, ask again: the answer reflects the new
site.

## Pre-merge Checklist

- [x] Have you checked for TypeScript, React or other console errors?
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