| name | promote |
| description | Promote agent-created skills and rules from NAS staging to tile GitHub repos via a full PR lifecycle — opens a PR, summons Copilot, iterates fixups until the review is clean, then merges so GHA publishes. Use when there are new items on staging, after check-staging shows pending items, or when asked to deploy skills, push to production, or publish rules to a tile repo. |
Promote from Staging
Runs the tile-repo PR lifecycle on content in the agent's NAS staging area: opens a PR, summons Copilot, iterates fixups, merges. End state is a merged PR on the tile repo; GHA (tessl publish, lint, skill review at 85%) runs on merge. Same review discipline as source-code PRs — no direct pushes to main, no merging before Copilot clears.
Before promoting
Run ./scripts/check-staging.sh to see what's pending. Review each item before promoting.
Determine the target tile
Each item belongs to exactly one tile:
| Content | Target tile | GitHub repo |
|---|
| Admin/operational skills | nanoclaw-admin | jbaruch/nanoclaw-admin (private) |
| Trusted shared operational | nanoclaw-trusted | jbaruch/nanoclaw-trusted |
| Security rules for untrusted | nanoclaw-untrusted | jbaruch/nanoclaw-untrusted |
| Shared behavior (all containers) | nanoclaw-core | jbaruch/nanoclaw-core |
| Host agent conventions | nanoclaw-host | jbaruch/nanoclaw-host |
Phase 1 — Open the PR
TILE_NAME=nanoclaw-admin ./scripts/promote-from-host.sh heartbeat
TILE_NAME=nanoclaw-admin ./scripts/promote-from-host.sh all
TILE_NAME=nanoclaw-trusted ./scripts/promote-from-host.sh --rules-only
The script:
- Clones the tile repo from GitHub
- Validates tile placement (blocks admin content from untrusted/core)
- Checks for cross-tile duplicates
- Copies skills and rules into the tile repo clone
- Runs
tessl skill review --optimize --yes on each promoted skill (shift-left — catches quality issues before PR)
- Creates a
promote/<timestamp>-<tile>-<hex> branch, commits, pushes
- Opens a PR (
--base main, --head <branch>) on the tile repo
- Summons Copilot via the GraphQL
requestReviews mutation (REST silently drops bot reviewers — see tile-repo-lib.sh)
The script prints PR opened: <url> and Branch: <name> — capture both. Step 5 requires tessl on the host machine; if unavailable, the script warns and skips (Copilot + GHA still gate).
Phase 2 — Wait for Copilot review
The pre-merge gates on a tile PR are Copilot review plus the local tessl skill review --optimize pass when tessl was available and Step 5 ran during Phase 1. If tessl was unavailable on the host and that local pass was skipped, the first tessl/lint gate happens post-merge in the tile repos' publish.yml workflow. That workflow runs on push to main, not on pull_request — so gh pr checks returns nothing and the 85% tessl skill review + lint + publish happen after merge. Don't wait for a green CI box that isn't coming; wait for Copilot.
gh api repos/jbaruch/<tile>/pulls/<N>/reviews \
--jq '.[] | select(.user.login | contains("opilot")) | {state, body: .body[:120]}'
gh api repos/jbaruch/<tile>/pulls/<N>/comments \
--jq '.[] | {path, line, body: .body[:200]}'
An empty reviews array means Copilot hasn't posted yet — wait. Expect a few minutes, sometimes longer under GitHub load.
Phase 3 — Fix what's fixable
Same discipline as ship-code:
- Copilot findings: apply what's right and reasonable; push back on anything that misreads scope or suggests over-engineering. Reply on every thread — accepted or declined — so nothing is left dangling.
Fix in staging, not in the tile clone. Otherwise the next re-promote of the same skill regresses the fix. Edit the NAS staging copy, then push the fixup onto the same branch:
TILE_NAME=<tile> ./scripts/push-staged-to-branch.sh \
<local-staging-dir> <tile> <branch> "<commit msg>" <skill|all|--rules-only>
Inside containers, the equivalent is the push_staged_to_branch MCP tool. Both call scripts/push-staged-to-branch.sh, which re-summons Copilot after pushing.
Reply on threads:
gh api "repos/jbaruch/<tile>/pulls/<N>/comments/<COMMENT_ID>/replies" \
-X POST -f body="Fixed in <sha> — <what changed>."
gh api "repos/jbaruch/<tile>/pulls/<N>/comments/<COMMENT_ID>/replies" \
-X POST -f body="Declining — <reason: out of scope / intentional / conflicts with X>."
Repeat Phase 2 + 3 until Copilot review is clean, all Copilot threads are replied to, and the local tessl skill review --optimize succeeds if available.
Phase 4 — Merge
Only when Copilot review is clean, all threads are replied to, and the local tessl skill review --optimize has succeeded if available:
gh pr merge <N> --repo jbaruch/<tile> --merge --delete-branch
GHA on main then runs publish.yml — 85% tessl skill review, tessl tile lint, and publish to the tessl registry. Watch it complete:
gh run list --repo jbaruch/<tile> --limit 1
If the post-merge review fails, the registry didn't get a new version but the bad content is on main. Open a follow-up PR to fix and run the cycle again — don't force-publish around a failing gate.
After merge
- Confirm the GHA publish run succeeded (tile version bump visible in the tessl registry).
- Tell the agent to run
/verify-tiles to clean up staging copies.
- For the
nanoclaw-host tile, run tessl update locally to pull the new version; for container tiles, the next ./scripts/deploy.sh picks them up.
Non-negotiables
- Always
--repo in every gh call. Defaults leak to upstream.
- Never push directly to
main on any tile repo — always PR.
- Never edit tile repos directly — all content flows through NAS staging → promote / push-staged pipeline.
- Never merge before Copilot clears — same gate as source-code PRs.