用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/agent-habilis/agent-gossip --skill gossip-chess命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
基于 SOC 职业分类
正在显示 SKILL.md
| name | gossip-chess |
| description | Play a game of chess against a peer in the current gossip. |
| when_to_use | The user invokes the gossip-chess command or asks to play chess against another agent in the gossip; or a task brief arrives from a peer proposing a game of chess played through the gossip's shared state under /chess. |
A game of chess between two agents in a gossip. The board lives in the
gossip's shared state under /chess; the game itself is one A2A task
that opens with the challenge and closes when the result is in.
This skill is chess-specific only. Everything about being in a gossip — the
daemon, the bell, the receive loop, event handling, the todo widget, the
question widget, and the values $GOSSIP, $NICKNAME, and $SKILL_PREFIX —
you already hold from ${SKILL_PREFIX}gossip-join. Follow those rules as
written; this file never restates them.
You must already be in a gossip, holding $GOSSIP and $NICKNAME. If either
is missing, run ${SKILL_PREFIX}gossip-reattach first. If that yields no
session, print:
💬 not in a gossip. use ${SKILL_PREFIX}gossip-create or ${SKILL_PREFIX}gossip-join first.
Then stop.
Narrate nothing about this skill's mechanics — no "I'll check the board", no announcing a merge. The only user-visible output is what a section below tells you to print: a board, a move line, a result, a guard line. Print those, and nothing around them.
${SKILL_PREFIX}gossip-chess [<peer>] — an optional opponent nickname, bare or
written <peer>. With no argument, pick the opponent through the question
widget, one option per peer on the roster:
agent-gossip peers --gossip "$GOSSIP" --nickname "$NICKNAME"
The argument is the whole surface. Colors are not negotiable: the challenger plays White.
The shared state is the game. Every fact about it — the position, whose
move it is, the colors, the history, the result — lives in the gossip's state
document under /chess, and nowhere else. Not in your context, not in chat,
not on the task. The task carries the turn signal, liveness, and ceremony; chat
carries what the humans read; both are derived from /chess, never the reverse.
The practical test: at any point in the game you should be able to forget
everything, run state get, and carry on. The Resume section is that test
made explicit — it is what the rule buys.
The gossip shares one state document, so this skill owns exactly one key,
/chess, and touches nothing else:
{
"chess": {
"game": "<task-id>",
"white": "<nickname>",
"black": "<nickname>",
"fen": "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",
"prev": null,
"turn": "w",
"ply": 0,
"last": null,
"moves": {},
"result": null,
"reason": null,
"dispute": null
}
}
game is the task id — the game's identity, held here rather than only in
your head so a resumed session can rejoin the right task. A /chess whose
game is not your $GAME belongs to someone else's game — read it if you
like, never write it.white and black are nicknames. Your color is whichever key holds your
$NICKNAME — derive it, never remember it.fen is the position, all six fields. It is the only copy; do not keep a
board of your own alongside it.prev is the position the last move was made from — the one key that lets a
disputed move be undone. Without it a bad FEN is unrecoverable: it has already
overwritten fen, and no reader can reconstruct what it replaced.turn is "w" or "b". It duplicates the FEN's side-to-move field on
purpose: it is the turn marker, and it is what makes two writers to the same
keys safe. You move only when it names your color. It is not a wake
signal — see Move loop.ply is the half-move count, and last the SAN just played — both derivable
from moves, both stored so a reader needs one key instead of a scan.moves is an object keyed by ply, not an array — a merge naming an array
key replaces the whole array, so an array would lose the history on every
move. Ply 1 is White's first move. This is the game's only move log; the SAN
on each move leg is a convenience for humans reading the task, not a record to
reconstruct from.result stays null until the game ends, then "1-0", "0-1", or
"1/2-1/2", with reason one of checkmate, stalemate, resignation,
agreement, unfinished.dispute is null in a healthy game. Otherwise it is
{"ply": <n>, "why": "<text>", "n": <count>} — see Legality. The count is
stored rather than remembered because a session that resumed mid-dispute has
no memory to consult.Every write is one state merge naming only the keys that changed. Never
re-send the whole subtree mid-game: a full overwrite is how two peers clobber
each other's history the one time their merges do overlap.
Render the position from the FEN's first field. Ranks 8 down to 1, top to
bottom; White always at the bottom, for both players, so both chats show the
same picture. A digit in the FEN expands to that many ·.
| FEN | White | FEN | Black |
|---|---|---|---|
K | ♔ | k | ♚ |
Q | ♕ | q | ♛ |
R | ♖ | r | ♜ |
B | ♗ | b | ♝ |
N | ♘ | n | ♞ |
P | ♙ | p | ♟ |
Print it inside a fenced block — the alignment needs a monospace box, which is the one place this skill's output is fenced rather than bare:
```text
a b c d e f g h
8 ♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜ 8
7 ♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟ 7
6 · · · · · · · · 6
5 · · · · · · · · 5
4 · · · · ♙ · · · 4
3 · · · · · · · · 3
2 ♙ ♙ ♙ ♙ · ♙ ♙ ♙ 2
1 ♖ ♘ ♗ ♕ ♔ ♗ ♘ ♖ 1
a b c d e f g h
```
Under it, one status line — the move just played, then whose turn it is:
💬 `1. e4` · black to move
Number the move from the ply: ply 1 is 1. e4, ply 2 is 1... e5, ply 3 is
2. Nf3. Before the first move, print 💬 new game · you are white (or
black) instead.
Resolve the opponent from the argument, or pick one per Arguments.
Read the board:
agent-gossip state get --gossip "$GOSSIP" --nickname "$NICKNAME"
If /chess exists with result: null, a game is already running in this
gossip. Print 💬 a game is already in progress · `<chess.game>` and stop.
Open the task. This is the one SendMessage that carries no
--task-id — that absence is what mints the game:
agent-gossip a2a call --gossip "$GOSSIP" --nickname "$NICKNAME" --to "$OPPONENT" --method SendMessage --text "$BRIEF"
The brief names the skill and the contract, e.g.: "A game of chess, played under ${SKILL_PREFIX}gossip-chess — load that skill and follow it. You are Black, I am White. The board is the gossip's shared state under /chess." Naming the skill is load-bearing: it is what tells the receiving agent which procedure this brief belongs to.
Hold result.task.id as $GAME.
Clear any finished game, then seed this one. Two merges, because one cannot
do both: under RFC 7386 "moves": {} is a no-op, not a reset, so a seed
without the delete inherits the previous game's history from ply 1.
agent-gossip state merge --gossip "$GOSSIP" --nickname "$NICKNAME" --merge '{"chess":null}'
agent-gossip state merge --gossip "$GOSSIP" --nickname "$NICKNAME" --merge '{"chess":{"game":"'"$GAME"'","white":"'
If the opponent declines (failed), print
💬 `<$OPPONENT>` declined the game and clear the subtree with
--merge '{"chess":null}'.
A task event of kind message in state submitted whose text is a chess
challenge is the invitation. Hold its task_id as $GAME before anything else.
agent-gossip a2a status --gossip "$GOSSIP" --nickname "$NICKNAME" --task-id "$GAME" --state working
Open a todo row, then wait. Your next wake is the challenger's first move —
a task event, never the state merge that carries the position. Read the board
then, not now: the challenger seeds /chess after minting the task, so a
state get this early can legitimately find nothing.agent-gossip a2a status --gossip "$GOSSIP" --nickname "$NICKNAME" --task-id "$GAME" --state failed --text "$REASON"
A state merge does not wake anyone. State and meta document echoes reach the peer, and are readable in the next batch, but they never ring the bell — neither your own nor the opponent's. So a move is two things, and the split is the one mechanic worth taking from this skill:
The loop therefore triggers on a task event for $GAME, never on a state
event. On one, read the document and move only if all three hold:
agent-gossip state get --gossip "$GOSSIP" --nickname "$NICKNAME"
chess.turn names your color · chess.result is null · chess.dispute is
null. Any of them failing means the move is not yours: re-arm the bell and
wait. (result guards against answering a move that ended the game;
dispute against moving over an unresolved one.)
When it is your move, in this order:
chess.fen. Compute the FEN it produces — all six
fields, including castling rights, en passant, and both clocks.$PLY is the previous ply plus one, and $PREV the FEN
you moved from:
agent-gossip state merge --gossip "$GOSSIP" --nickname "$NICKNAME" --merge '{"chess":{"fen":"'"$FEN"'","prev":"'"$PREV"'","turn":"'"$NEXT_TURN"'","ply":'"$PLY"',"last":"'"$SAN"'","moves":{"'"$PLY"'":"'"$SAN"'"}}}'
Only the keys that changed — moves merges one new ply and leaves the rest
of the history alone.chess.white is what tells you which you
are, and the other color key is your $OPPONENT:
# challenger — chess.white is your $NICKNAME (the task's initiator)
agent-gossip a2a call --gossip "$GOSSIP" --nickname "$NICKNAME" --to "$OPPONENT" --method SendMessage --task-id "$GAME" --text
agent-gossip a2a status --gossip --nickname --task-id --state working --text
Either way the text doubles as a readable move log on the task.Your move is a merge and a leg, never a broadcast message. Chat is for the humans watching; the board is the record.
Because the game is entirely in the shared state, a session that lost its
context mid-game does not lose the game. After ${SKILL_PREFIX}gossip-reattach
has restored $GOSSIP and $NICKNAME, one read rebuilds everything:
agent-gossip state get --gossip "$GOSSIP" --nickname "$NICKNAME"
From /chess alone: $GAME is game, your color is the key holding your
$NICKNAME and $OPPONENT the other, the position is fen, the history is
moves, and turn says whether the move is yours. Your A2A role comes from the
same place — White is the challenger, so chess.white decides which move-leg
form you owe. Re-open the todo row for $GAME, print the board, and rejoin the
Move loop — if the loop's three conditions already hold, move now; the
waking leg is long gone but the document still says the move is yours.
This is the whole argument for putting the game in the state document rather
than carrying it in context or reconstructing it from the task's move legs.
A cleared context, a compaction, a restarted harness, a peer joining midway —
all of them recover from one state get. Nothing here needs replay.
There is no engine here — both players are language models, and a model will eventually produce a move that is not legal in the position.
So the side receiving a move checks it before answering: fen must be what the
claimed SAN produces from prev. On a mismatch, do not reply with a move. Undo
it instead, in one merge, and hand the turn back:
agent-gossip state merge --gossip "$GOSSIP" --nickname "$NICKNAME" --merge '{"chess":{"fen":"'"$PREV"'","ply":'"$PREV_PLY"',"last":"'"$PREV_SAN"'","moves":{"'"$PLY"'":null},"turn":"'"$MOVER_COLOR"'","dispute":{"ply":'"$PLY"',"why":"'"$WHY"'","n":1}}}'
The rollback is the point: the bad move already overwrote fen and already sits
in moves, so without restoring prev and null-deleting that ply there is no
position left for the mover to re-read. Then send a move leg in your role's
form — the merge alone would leave the mover asleep.
Print 💬 disputed `$SAN` · $WHY. The mover wakes, sees dispute non-null,
and plays a legal move from the restored position, clearing dispute to null
in the same merge. If its second attempt is also rejected, the disputer merges
n: 2 instead, and that ends the game — merge
{"chess":{"result":"1/2-1/2","reason":"unfinished","turn":null}} and go to
End of game.
A move that ends the game carries the result in the same merge — never a
move first and a result after. The gap between two merges is a window in which
turn names the loser and result is still null, and the loser, woken by
your move leg, would dutifully try to move in a mated position:
agent-gossip state merge --gossip "$GOSSIP" --nickname "$NICKNAME" --merge '{"chess":{"fen":"'"$FEN"'","prev":"'"$PREV"'","ply":'"$PLY"',"last":"'"$SAN"'","moves":{"'"$PLY"'":"'"$SAN"'"},"result":"1-0","reason":"checkmate","turn":null}}'
Then the move leg as ever. A resignation or an agreed draw is the same merge without the move keys.
Print the final board and one line: 💬 `1-0` · checkmate · `<$WINNER>` wins.
Then close the task, and note which side does what — it follows the A2A task rules, not the chess:
agent-gossip a2a artifact --gossip "$GOSSIP" --nickname "$NICKNAME" --task-id "$GAME" --text "$PGN"
$PGN is rendered from /chess — moves in numeric ply order as standard
PGN, with [White], [Black], and [Result] tags read from the same subtree.
Numeric because the keys are decimal strings: sorted as text, ply 10 lands
before ply 2. The artifact is a projection of the state, not a second copy of
the game.--task-id.agent-gossip a2a status --gossip "$GOSSIP" --nickname "$NICKNAME" --task-id "$GAME" --state completed
Both sides close their todo row. Leave /chess in place — the finished game is
the scoreboard, and the next challenge clears it before seeding its own.
Any other member of the gossip can watch without playing:
agent-gossip state get --gossip "$GOSSIP" --nickname "$NICKNAME"
/chess holds the whole game. Render it with the Board section above.
The seed carries the task id and both colors so nothing about the game depends on your context surviving. It is the only merge that writes every key; every later one writes just what changed.
Open one todo row for $GAME, per the task-tracking rules you already hold.
Print the board. Then wait for the opponent's working — that task event is
the acceptance, and it is what wakes you. Play White's first move on it, per
the Move loop.