Skip to content

grok-bot onboarding: a docs pointer on every CLI path + the grok-bot-operate skill - #982

Merged
orveth merged 5 commits into
MakePrisms:mainfrom
maxy-player:feat/grok-bot-onboarding
Sep 8, 2026
Merged

grok-bot onboarding: a docs pointer on every CLI path + the grok-bot-operate skill#982
orveth merged 5 commits into
MakePrisms:mainfrom
maxy-player:feat/grok-bot-onboarding

Conversation

@maxy-player

@maxy-player maxy-player commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Base: ec95eb2e995d357b5382e46d0efb3025f7d803a0 (upstream/main, 2026-09-08 11:49 +0200). Branch feat/grok-bot-onboarding, 5 commits, 0 merges. Governing brief + Addendum 1 + erratum + item-4 ruling + Addendum 2: forge briefs/grok-bot-onboarding-*-20260908T1{515,521,525,752,813}Z.md.

Why

A Grok Bot box is an operator, not a harness (BUILTIN_PRESETS = ["claude", "cursor", "codex"]). On the base commit the whole binary carried exactly one pointer to the docs, and it sat in the MCP initialize.instructions preamble. Measured on a real box (2026-09-08): the string was delivered, the bot could quote it back, and it still treated "connected + 4 tools" as setup being finished and went straight to post_job. A seller never touches MCP, so it had no route to the docs at all.

What changed

Code (Part A + Addendum 1 Part A3)

  • crates/maxplayer/src/skill.rs (new): SKILL_URL and SKILL_INDEX_URL constants, docs_pointer_line(), and render()/run() for a new maxplayer skill subcommand that prints the orientation URL and the machine-readable index to stdout. No wallet, no key, no network. Unit-tested against the constants, not a hand-copied string.
  • cli.rs: maxplayer skill registered; maxplayer --help (and the usage text on stderr for an unknown command) carries docs_pointer_line().
  • doctor.rs: doctor --help and the doctor run output carry the pointer.
  • sell.rs: seller first-run (the turn that writes [seller]) prints the pointer once; a steady-state relaunch does not repeat it; seller --help carries it.
  • mcp.rs: INSTRUCTION_GUIDES is now composed from SKILL_URL (instruction_guides()), and with_guides() appends the same sentence to the tool descriptions of post_job and collect only. get_job and award are deliberately left alone per Addendum 1 item 3; I do not think a third carrier is needed.
  • main.rs: mod skill;.
  • Tests: each surface has a test that fails if the pointer disappears; the mcp tests assert the handshake text exactly and that only two tool descriptions carry the sentence.

Docs (Part B) — the skill in all four registries

  • web/app/.well-known/skills/grok-bot-operate/skill.md — the artifact. Opens on "Grok Bot is an operator, not a harness" with the preset list. Step 1 is read this page before your first post_job, with the observed reason: the MCP handshake does deliver the link, once, at connect, and it is easy to skim past. The page does not claim the client drops instructions. Covers: what a Grok Bot box is (one persistent shared Linux VM, no Docker, no systemd), buyer path (wallet setup and mint-complete, maxplayer mcp with an absolute binary path and explicit MAXPLAYER_HOME, post_job → get_job → collect, posting is the spend decision and the daemon auto-awards), seller path (cursor-agent pinned to a grok model, [sandbox] mode = "launcher" over bubblewrap, Nix started by hand), the four footguns with the fix that worked, and the ~/AGENTS.md operator step. No secrets, no invoice/quote ids.
  • web/app/.well-known/skills/index.json{name, description, path} entry maxplayer-grok-bot-operate (drives the discovery index + sha256 digests).
  • web/app/.well-known/skills/default/skill.md — the homepage skill (which is /skill.md, the handshake URL) now links [grok-bot-operate](/.well-known/skills/grok-bot-operate/skill.md); "five companion skills" → "six".
  • web/app/public/llms.txt — entry under "Agent skills".

Acceptance gates (run on head 6c1c5ab; re-run unchanged on 889f149, which touches no file under crates/)

Part A gate — brief: git grep -n 'maxplayer.ai/skill.md' -- crates/ returns hits in the help, doctor and seller paths, not only mcp.rs

$ git grep -n 'maxplayer.ai/skill.md' -- crates/
crates/maxplayer/src/cli.rs:674:    // carried exactly one route to https://www.maxplayer.ai/skill.md, the MCP handshake text — so
crates/maxplayer/src/doctor.rs:2555:    // runs when something is wrong — carried no route to https://www.maxplayer.ai/skill.md; only the
crates/maxplayer/src/mcp.rs:779:            "Setup, operation, and debugging guides: https://www.maxplayer.ai/skill.md — start there before first use.\n",
crates/maxplayer/src/mcp.rs:792:            "Setup, operation, and debugging guides: https://www.maxplayer.ai/skill.md — start there before first use.\n",
crates/maxplayer/src/sell.rs:810:    // handshake text — until now the binary's only route to https://www.maxplayer.ai/skill.md — never
crates/maxplayer/src/skill.rs:27:pub const SKILL_URL: &str = "https://www.maxplayer.ai/skill.md";

Read honestly: the literal URL has exactly one non-test hit, skill.rs:27. The other five are inside mod tests (cli.rs tests start at 523, doctor.rs at 2551, sell.rs at 801, mcp.rs at 557). That is the brief's item 3 working as intended: one shared constant, so the literal cannot drift. The help, doctor and seller paths therefore show up under the symbol grep, not the literal one:

$ git grep -nE 'SKILL_URL|docs_pointer_line|instruction_guides|with_guides' -- crates/
crates/maxplayer/src/cli.rs:348:        crate::skill::docs_pointer_line()
crates/maxplayer/src/cli.rs:682:        let url = crate::skill::SKILL_URL;
crates/maxplayer/src/doctor.rs:2175:        crate::skill::docs_pointer_line()
crates/maxplayer/src/doctor.rs:2188:        crate::skill::docs_pointer_line()
crates/maxplayer/src/doctor.rs:2561:        let url = crate::skill::SKILL_URL;
crates/maxplayer/src/mcp.rs:134:fn instruction_guides() -> String {
crates/maxplayer/src/mcp.rs:137:        crate::skill::SKILL_URL
crates/maxplayer/src/mcp.rs:153:fn with_guides(description: &str) -> String {
crates/maxplayer/src/mcp.rs:154:    format!("{description} {}", instruction_guides())
crates/maxplayer/src/mcp.rs:158:    let guides = instruction_guides();
crates/maxplayer/src/mcp.rs:228:            "description": with_guides("Publish a real maxplayer job offer (OFFER kind) to the configured maxplayer relay, then let the buyer daemon drive the award: once a payable seller claim appears the daemon auto-awards it under the hood, so the normal flow is just post_job then collect (two calls). max_sats caps what the daemon will commit to (defaults to amount_sats); it never auto-awards a claim it cannot pay. harness, harness_family, model and capabilities are ALL hard award filters (only a seller advertising them can be awarded), enforced identically on the manual and automatic award paths; model requires harness (the preset), and a harness_family given alongside harness must name the same harness it does. Omit them all and every claim passes exactly as before. Targeted seller p-tag is the documented default (pass seller_pubkey); set untargeted=true for an open offer. Optional repo+branch attach git delivery tags. CONTRIBUTION (freelance-PR) mode: supply target_repo_owner + target_repo_url + base_branch + base_oid to post a job-class=contribution offer against a repo you own (seller forks it and delivers a PR); these four are ALL-OR-NOTHING (a partial set is refused). Omit all four ⇒ from-scratch job. PAYMENT MODE: payment defaults to \"sat\" — a priced job that commits real money at award and needs a funded wallet. payment=\"none\" posts a FREE job instead: it requires amount_sats=0, commits nothing, needs no wallet and no mint, and is awarded only to a seller whose claim also says none (a seat advertising takes_no_payment). A free job settles through collect exactly like a priced one, but pays nothing. Never echoes secrets."),
crates/maxplayer/src/mcp.rs:316:            "description": with_guides("Single-call buyer collect: if no accept-bind exists yet, accept the delivered claim itself (fetch the seller's result from the relay and record the co-signed pay-bind — the same accept path `maxplayer accept` runs), verify the delivery integrity (the delivered branch must tip at the accepted commit — the PayPathDeliveryVerifier tip-match — and the delivered tree must carry this job's execution sentinel), then materialize the files into <home>/results/<job_id>. What happens between verify and materialize depends on the mode the offer and claim AGREED on at accept, which collect reads off the local bind and never infers: a PRICED job (payment=sat) is auto-paid through the sealed money path (BudgetGate → PaymentService::run, single-redeem + mint-compat intact), and if the wallet holds no funds it refuses with a message pointing at `maxplayer wallet setup`. A FREE job (payment=none) pays nothing: no wallet is opened, no mint is contacted, no budget is charged, and the durable spend ledger is never even READ, so a buyer with no wallet at all — or with an unreadable spend ledger — can collect one; the integrity checks above still run in full, and a delivery that fails them materializes nothing. On integrity mismatch or a bad seller co-signature: refuses and does NOT pay. Idempotent: re-collecting re-materializes without a second payment. Returns {pay: {state, attempt_id, amount_sats, spent_total_sats}, commit_oid, path, files, agent_used, model_used}; for a free collect pay.state is \"none\", pay.attempt_id is null and pay.amount_sats is 0, because no payment attempt was ever made, and pay.spent_total_sats is ABSENT — a free collect reads no spend ledger, so it reports no total rather than a 0 that would read as \"you have spent nothing\"; read the standing total from `buyer status`. agent_used/model_used are the seller-claimed harness/model that produced the delivered result (null = the seller reported nothing; an attribution, never a verification). agent_used is the RESOLVED harness id (e.g. claude-agent-acp), a different vocabulary from post_job's harness label (claude) — never string-compare the two. Never echoes secrets."),
crates/maxplayer/src/mcp.rs:732:        let guides = instruction_guides();
crates/maxplayer/src/mcp.rs:733:        assert!(guides.contains(crate::skill::SKILL_URL));
crates/maxplayer/src/mcp.rs:753:                text.matches(crate::skill::SKILL_URL).count(),
crates/maxplayer/src/mcp.rs:768:                !text.contains(crate::skill::SKILL_URL),
crates/maxplayer/src/sell.rs:552:        let _ = writeln!(err, "{}", crate::skill::docs_pointer_line());
crates/maxplayer/src/sell.rs:796:        crate::skill::docs_pointer_line()
crates/maxplayer/src/sell.rs:816:        let url = crate::skill::SKILL_URL;
crates/maxplayer/src/skill.rs:27:pub const SKILL_URL: &str = "https://www.maxplayer.ai/skill.md";
crates/maxplayer/src/skill.rs:35:pub fn docs_pointer_line() -> String {
crates/maxplayer/src/skill.rs:36:    format!("Docs for agents: {SKILL_URL}  (or run `maxplayer skill`)")
crates/maxplayer/src/skill.rs:62:         \x20 orientation:  {SKILL_URL}\n\
crates/maxplayer/src/skill.rs:99:        assert!(out.contains(SKILL_URL), "orientation URL missing:\n{out}");
crates/maxplayer/src/skill.rs:120:        assert!(SKILL_URL.starts_with(origin));
crates/maxplayer/src/skill.rs:125:    fn docs_pointer_line_carries_the_shared_url_and_names_the_subcommand() {
crates/maxplayer/src/skill.rs:126:        let line = docs_pointer_line();
crates/maxplayer/src/skill.rs:127:        assert!(line.contains(SKILL_URL));

Addendum 1 Part A3 gate, verbatim

Gate: on the delivered head, git grep -n 'maxplayer.ai/skill.md' -- crates/ must show the shared constant's definition, the post_job and collect description sites (or the one call site that composes both), the Part A CLI surfaces, and the tests — and the count of non-test hits must be strictly greater than the one hit ec95eb2 has. Print that grep, verbatim, in the PR body.

Status against it: the constant's definition ✔ and the tests ✔ appear in the literal grep above. The post_job/collect composing site (with_guidesinstruction_guidesSKILL_URL, mcp.rs:134–154) and the CLI surfaces appear in the symbol grep, because they reference the constant rather than repeat the literal. The "strictly greater than one non-test literal hit" clause is not met and cannot be met without adding a second copy of the URL, which the brief's item 3 forbids and the web build already refuses for the same reason. Base ec95eb2 had one non-test literal hit (mcp.rs:31, INSTRUCTION_GUIDES); this head has one (skill.rs:27, SKILL_URL). Flagging this for the ordering seat rather than resolving it by duplicating the string.

Ruled: the ordering seat struck the count clause on 2026-09-08 17:52Z (briefs/grok-bot-onboarding-addendum1-RULING-item4-20260908T1752Z.md); item 4 now requires exactly one non-test literal hit (the constant) plus the symbol grep showing the post_job/collect sites and each Part A CLI surface, both verbatim. This head meets the gate as ruled; no code change was ordered.

Part B proof — run this turn in web/app

$ npm run build      → exit 0   (dist/terminal.js 49.2 KB · stamp f2587dec63a3)
$ npm test           → exit 0
ℹ tests 92
ℹ pass 92
ℹ fail 0

including test/build.test.mjs "the homepage skill links every companion skill the index publishes" — the roll-call that derives its expectation from index.json and fails until the homepage links the new skill.

Rust: cargo test -p maxplayer --bin maxplayer -- skill:: cli::tests doctor::tests sell::tests mcp::tests → 146 passed, 0 failed (this turn).

Addendum 2 — advisor round 1 DENY fixes, head 889f149 (append-only on 6c1c5ab; no rebase, no force-push)

Fix 1 (F1, body only): the symbol grep above is now the unabridged 28-line output, both long with_guides( description lines (mcp.rs:228 and :316) complete to their closing "),; the truncation note is gone; the item-4 status cites the 17:52Z ruling.

Fix 1 gate, run on the live body 2026-09-08 ~18:55Z, before this block was added (the two commands quoted here contain the pattern themselves, so a re-count after this edit includes them):

$ gh pr view 982 --repo MakePrisms/maxplayerai --json body --jq .body | grep -c 'with_guides("'
2
$ gh pr view 982 --repo MakePrisms/maxplayerai --json body --jq .body | grep 'with_guides("' | grep -c '"),$'
2

Fix 2 (F2, docs only, 889f149): the [sandbox] launcher sample in grok-bot-operate/skill.md bound the agent's paths but not ~/.local/bin/maxplayer, where step 1 installs the binary. run_under_launcher (crates/maxplayer/src/sandbox_probe.rs) executes current_exe() inside the launcher, so under that sample maxplayer doctor fails containment with ENOENT while the page says it must PASS. One --ro-bind of the binary at its own absolute path now sits next to the cursor-agent binds, with a paragraph after the sample naming why. $MAXPLAYER_HOME stays unbound, the seller-jobs bind stays narrow, no whole-home bind, no --unsafe-no-sandbox. The page has one launcher sample (8 --ro-bind lines, all in that block).

Fix 2 gates, run on 889f149:

$ git grep -n '.local/bin/maxplayer' -- web/app/.well-known/skills/grok-bot-operate/skill.md
web/app/.well-known/skills/grok-bot-operate/skill.md:68:mkdir -p ~/.local/bin && cp maxplayer-$V-$P/maxplayer ~/.local/bin/maxplayer && chmod +x ~/.local/bin/maxplayer
web/app/.well-known/skills/grok-bot-operate/skill.md:101:- **command:** the **absolute** path to the binary, e.g. `/home/<user>/.local/bin/maxplayer` — the
web/app/.well-known/skills/grok-bot-operate/skill.md:177:  "--ro-bind", "/home/<user>/.local/bin/maxplayer", "/home/<user>/.local/bin/maxplayer",
web/app/.well-known/skills/grok-bot-operate/skill.md:185:The `~/.local/bin/maxplayer` bind is not optional: the containment probe that `doctor` runs executes

$ (cd web/app && npm run build)   → exit 0   (dist/terminal.js 49.2 KB · stamp f2587dec63a3)
$ (cd web/app && npm test)        → exit 0
ℹ tests 92
ℹ pass 92
ℹ fail 0

Rust is untouched by 889f149, so no cargo run was required for it.

CI on 889f149: run 34262534726 failed one job, "Test the full shipped feature combo (acp + wallet)", on one test, seller_node::run::tests::a_losing_open_pool_claimant_releases_its_slot_when_it_sees_the_award, panic at crates/maxplayer-core/src/seller_node/run.rs:11878. This PR touches no file under crates/maxplayer-core/, and 889f149 changed only web/app/.well-known/skills/grok-bot-operate/skill.md (+8/−0); the identical Rust tree at 6c1c5ab passed that same job in run 34259482953. The failure is not attributable to this PR. The other six code checks are green; the Vercel "Authorization required" check is the fork-branch deploy gate and clears at merge. Not re-run and the test not touched: a human decides whether to re-run or merge over it.

Field-note claims dropped for lack of confirmation against ec95eb2

Not in the page because I could not verify them in the code or from a source I could check: the Determinate nix-installer flags (--init none, determinate-nixd daemon &); NO_OPEN_BROWSER=1; cursor-agent status; the model id cursor-grok-4.6-high; the contribution_enabled / slots = 3 / [profile] config sample; writable .cursor / .cache binds and the mkdir .cursor/projects symptom; the exact --model pin on cursor-agent (kept as a <grok model id> placeholder); the harness: "codex" Wally anecdote; the ~/.local/bin/agent symlink.

Out of scope — named, not touched

  • INSTRUCTION_NIX_MISSING (mcp.rs) still tells the operator to curl … | sh, which a Grok Bot box's approval gate blocks. Needs a second, non-piped route. Separate change.
  • Claude and codex onboarding pages (lane narrowed to Grok Bot).
  • Anything in npm/, and PR feat(plugin): ship the buyer playbook as a Claude Code plugin #938 (feat/buyer-plugin-skill).

Formatting note

cargo fmt --check is dirty on ec95eb2 in 93 files, including cli.rs, doctor.rs, sell.rs and mcp.rs before this branch touched them. This PR does not reformat them. The only fmt change here is 6c1c5ab, which reflows the assert! lines this branch itself added so the branch introduces no new drift (per-file rustfmt hunk count at head equals the count at base for every touched file; skill.rs is clean).

w-grok-bot-onboarding added 4 commits September 8, 2026 08:25
…r first-run

Measured on ec95eb2: the whole binary carried exactly one route to the docs,
the MCP initialize.instructions text in mcp.rs (mcp.rs:31, tests at :701/:714).
A buyer that registers `maxplayer mcp` was told where the guides are; nobody
else ever was — not `maxplayer --help`, not `maxplayer doctor`, not a seller
configuring a new seat — and mcp.rs:47 records that a client may discard
`instructions`. A seller-only box never touches MCP, so it had no route at all.

- New `crates/maxplayer/src/skill.rs`: `SKILL_URL`, `SKILL_INDEX_URL`, the
  shared `docs_pointer_line()`, and `maxplayer skill`, which prints the
  orientation URL and the machine-readable index to stdout. Pure: no home,
  key, wallet or network.
- The same one-line pointer on the three paths that carried none: the
  top-level usage (stdout on --help, stderr on a wrong invocation), the doctor
  report preamble and `doctor --help`, and the seller first-run path after
  `wrote [seller]` (new seat only; relaunches stay quiet) plus `seller --help`.
- mcp.rs INSTRUCTION_GUIDES now formats from the shared constant instead of
  carrying a second copy of the URL. Handshake text unchanged; the exact-text
  tests still pass.
- Tests assert against the shared constant, never a hand-copied URL:
  skill::tests (4), cli::tests::help_and_skill_carry_the_docs_pointer,
  doctor::tests::doctor_usage_and_report_preamble_carry_the_docs_pointer,
  sell::tests::first_run_writes_seller_and_points_at_the_docs_once, and
  `skill --help` joins the MakePrisms#570 every-subcommand help roll-call.
Addendum 1, Part A3. Field evidence from a real Grok Bot box (2026-09-08):
the handshake `instructions` WERE delivered — the agent could quote the guides
line back later — and were still skimmed past. It summarised registration as
"connected + 4 tools", went straight to hiring, and reached skill.md only
under debugging pressure after it had started spending. A one-shot preamble
is read like a banner; a tool description is read like an instruction, in the
turn the agent decides to call that tool.

- `with_guides()` appends the ONE shared guides sentence (built from
  `crate::skill::SKILL_URL`, the same text the handshake prints) as the final
  sentence of exactly two descriptions: `post_job`, the first tool a new buyer
  calls and the one that spends money, and `collect`, the tool reached for
  when something has gone wrong. Existing text is neither rewritten nor
  reflowed.
- `get_job` and `award_claim` are deliberately left alone — two carriers are a
  pointer, four are noise — and the test asserts them clean.
- Test `post_job_and_collect_descriptions_end_with_the_guides_pointer`:
  both carriers END with the shared sentence verbatim, carry the URL exactly
  once, and keep their original closing sentence intact; the other two tools
  do not carry the URL. No hand-copied URL anywhere.
Part B of the grok-bot onboarding brief. A Grok Bot box is an OPERATOR of the
marketplace, not a seller harness — the presets are exactly claude, cursor and
codex — and the page says so in its first paragraph. It opens on the mistake a
real box made on 2026-09-08: the MCP handshake DID deliver the guides pointer,
once, at connect; the agent read "connected · 4 tools" as setup being finished
and went straight to hiring. Step 1 is therefore "read this page before your
first post_job", stated as a step with that reason. The page does not claim the
client drops `instructions` — it does not.

Registered in all four surfaces, or it ships unreachable:
- the artifact, web/app/.well-known/skills/grok-bot-operate/skill.md;
- web/app/.well-known/skills/index.json ({name, description, path}) — the
  build derives the RFC v0.2.0 discovery index and every sha256 digest from it;
- web/app/.well-known/skills/default/skill.md (which IS /skill.md, the URL the
  handshake points at) now links [grok-bot-operate](…) — the top-down route;
- web/app/public/llms.txt under "Agent skills".

Content: what the box is (one persistent Linux VM per account, shared by every
bot, no Docker, no systemd) and why that changes the instructions; the buyer
path (tarball install verified against SHA256SUMS because the approval gate
blocks `curl | sh`, wallet setup AND mint-complete, `maxplayer mcp` with an
absolute binary path and explicit MAXPLAYER_HOME, tools on the next turn,
post_job → get_job → collect, posting is the spend decision); the seller path
(cursor-agent pinned to a grok model as the `cursor` preset, `[sandbox]
mode = "launcher"` over bubblewrap, Nix started by hand); the four footguns
with the fix that worked; one operator-written ~/AGENTS.md carrying the two
URLs. Every command, flag, path and config key was checked against ec95eb2;
field-note claims that could not be confirmed against the repo were dropped
(listed in the PR body). No secrets, tokens, auth-file contents, invoice or
quote ids.

Verified in this turn (web/app): `npm run build` OK, `npm test` 92 pass /
0 fail, including "the homepage skill links every companion skill the index
publishes" — the roll-call test that is the reachability proof.
@vercel

vercel Bot commented Sep 8, 2026

Copy link
Copy Markdown

Someone is attempting to deploy a commit to the MakePrisms Team on Vercel.

A member of the Team first needs to authorize it.

…octor's probe can run

Addendum 2 Fix 2 (advisor round 1 F2). The bubblewrap sample bound /usr, /lib,
/lib64, /bin, two /etc paths, the cursor-agent tree, .config/cursor and
seller-jobs, but not ~/.local/bin/maxplayer, where step 1 installs the binary.
run_under_launcher in sandbox_probe.rs executes current_exe() inside the
launcher, so under the sample `maxplayer doctor` fails containment with ENOENT
while the page tells the reader it must PASS.

One read-only bind of the binary at its own absolute path, next to the
cursor-agent binds, plus a paragraph naming why. $MAXPLAYER_HOME stays unbound,
the seller-jobs bind stays narrow, no whole-home bind, no --unsafe-no-sandbox.
Docs only; no Rust touched.
@vercel

vercel Bot commented Sep 8, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
mobee Ready Ready Preview Sep 8, 2026 8:28pm UTC

Request Review

@orveth
orveth merged commit d55ceaf into MakePrisms:main Sep 8, 2026
8 of 9 checks passed
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