novusedge/palpatine/adversary

Claude Code and agent skill by novusedge/palpatine

Trust tier 2
  • community
LLM review pending
Source check pending

Checking that the SKILL.md file is still in the repository.

Subagent orchestration for sophisticated opponent modeling and multi-party analysis.

Install

npx vskill@latest install novusedge/palpatine/adversary

SKILL.md

Adversary Simulation

Invoked via Claude Code /palpatine:adversary or Codex $palpatine:adversary, or auto-triggered for:

  • Wargames with sophisticated opponents
  • Multi-party scenarios (3+ players)
  • Counter mode with complex stakeholder dynamics

When to Use Subagents

Use subagents when:

  • Multiple independent perspectives needed simultaneously
  • Opponent sophistication warrants dedicated modeling
  • User wants deep multi-party analysis

Don't use subagents when:

  • Single obvious opponent
  • Simple 2-party dynamics
  • Quick read is sufficient

Host Agent Patterns

Schemas

Use these schemas in the controller. Agents return raw JSON; the controller parses it, validates the exact shape, and requests one correction before treating the response as a gap.

javascript
// Single adversary response
const ADVERSARY_SCHEMA = {
type: "object",
properties: {
counter: {
type: "string",
description: "Their response move, not reasoning"
},
exploits: {
type: "array",
items: { type: "string" },
maxItems: 3,
description: "Target weaknesses they'd hit"
},
escalation: {
type: "string",
description: "How they escalate if resisted"
},
weakPoint: {
type: "string",
description: "Where they're exposed"
}
},
required: ["counter", "exploits", "escalation", "weakPoint"]
}
// Multi-party player analysis
const PLAYER_SCHEMA = {
type: "object",
properties: {
move: { type: "string" },
alliance: {
type: "string",
description: "Who they side with and why it serves them"
},
threat: {
type: "string",
description: "How they could hurt target"
},
price: {
type: "string",
description: "Cost to neutralize or buy them off"
},
threatLevel: {
type: "string",
enum: ["high", "medium", "low"]
}
},
required: ["move", "alliance", "threat", "price", "threatLevel"]
}
function validateJsonObject(rawResponse, schema) {
let response;
try {
response = typeof rawResponse === "string"
? JSON.parse(rawResponse)
: rawResponse;
} catch {
return null;
}
if (response === null || typeof response !== "object" || Array.isArray(response)) {
return null;
}
const expectedKeys = [...schema.required].sort();
const actualKeys = Object.keys(response).sort();
if (
actualKeys.length !== expectedKeys.length ||
actualKeys.some((key, index) => key !== expectedKeys[index])
) {
return null;
}
for (const key of schema.required) {
const definition = schema.properties[key];
const value = response[key];
if (definition.type === "string" && typeof value !== "string") return null;
if (
definition.type === "array" &&
(!Array.isArray(value) ||
value.some((item) => typeof item !== definition.items.type) ||
(definition.maxItems !== undefined && value.length > definition.maxItems))
) {
return null;
}
if (definition.enum && !definition.enum.includes(value)) return null;
}
return response;
}
Host Adapters

Codex:

  • Use spawn_agent with fork_turns: "none" and all player context in the prompt.
  • Ask for the exact keys move, alliance, threat, price, and threatLevel.
  • Use list_agents to derive current capacity, choose an unused run component, and read completed payloads keyed by the canonical task names returned from spawn_agent.
  • Use wait_agent only as a mailbox notification, followup_task for one correction, and interrupt_agent for workers still pending at the deadline. Leaf workers never spawn.
  • Inherit the orchestrator model. Forward userRequestedModel only when the user explicitly named a model; never infer an override.

Claude Code:

  • Call Agent with required description and prompt fields. subagent_type is optional. The current tool does not accept schema.
  • Request raw JSON in the prompt, then validate it against PLAYER_SCHEMA or ADVERSARY_SCHEMA in the controller and request one correction if needed.
  • Use Promise.all only for independent players.
Single Adversary

Spawn one agent for focused opponent modeling:

javascript
async function runClaudeAdversary() {
let rawResponse = await Agent({
description: "Adversary: [role]",
prompt: `Model [OPPONENT] as ruthless rational actor.
OPPONENT: [role/name]
GOALS: [what they want — specific]
RESOURCES: [leverage, relationships, info, authority]
CONSTRAINTS: [what stops them from going nuclear]
TARGET is about to: [user's planned move]
Assume competent and self-interested. What's their counter-move?
Return only raw JSON with exactly these keys: counter, exploits, escalation, weakPoint.
No Markdown fences. Most likely play, stated cold.`
});
let response = validateJsonObject(rawResponse, ADVERSARY_SCHEMA);
if (!response) {
rawResponse = await Agent({
description: "Correct adversary JSON",
prompt: `The prior response was invalid: ${rawResponse}
Return only raw JSON with exactly these keys: counter, exploits, escalation, weakPoint.`
});
response = validateJsonObject(rawResponse, ADVERSARY_SCHEMA);
}
if (!response) throw new Error("Claude adversary returned invalid JSON after correction.");
return response;
}
Multi-Party (Bounded Waves)

Dispatch only independent players. At most five player models run per invocation, and each wave is capped by current worker capacity:

javascript
const CODEX_TEAM_SLOT_LIMIT = 4;
function chooseUnusedRunComponent(agents, taskPrefix) {
const existingLeafNames = agents.map((agent) => agent.agent_name.split("/").at(-1));
for (let runIndex = 0; ; runIndex += 1) {
const runComponent = `r${runIndex}`;
const reservedPrefix = `${taskPrefix}_${runComponent}_`;
if (!existingLeafNames.some((name) => name.startsWith(reservedPrefix))) {
return runComponent;
}
}
}
function agentConsumesCodexSlot(agent) {
const status = agent.agent_status;
if (["pending", "running", "working"].includes(status)) return true;
return status !== null &&
typeof status === "object" &&
!Object.prototype.hasOwnProperty.call(status, "completed");
}
function availableCodexWorkerSlots(agents) {
const activeAgentCount = agents.filter(agentConsumesCodexSlot).length;
return Math.max(0, CODEX_TEAM_SLOT_LIMIT - activeAgentCount);
}
function hasExactKeys(value, expectedKeys) {
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
const actualKeys = Object.keys(value).sort();
const sortedExpectedKeys = [...expectedKeys].sort();
return actualKeys.length === sortedExpectedKeys.length &&
actualKeys.every((key, index) => key === sortedExpectedKeys[index]);
}
async function runMultiPartyAdversary({
situation,
explicitlyRequestedModel,
}) {
const players = [
{ name: "CEO", goals: "...", leverage: "..." },
{ name: "HR Director", goals: "...", leverage: "..." },
{ name: "Skip-level", goals: "...", leverage: "..." }
];
const userRequestedModel = explicitlyRequestedModel;
const PLAYER_OUTPUT_KEYS = ["move", "alliance", "threat", "price", "threatLevel"];
const MAX_PLAYER_MODELS = 5;
const COLLECTION_TIMEOUT_MS = 120_000;
const COLLECTION_TIMEOUT_DESCRIPTION = `${COLLECTION_TIMEOUT_MS / 1_000} seconds`;
const remainingPlayers = players.slice(0, MAX_PLAYER_MODELS);
const results = [];
const collectionGaps = players.slice(MAX_PLAYER_MODELS).map((player) => ({
workerTask: null,
player: player.name,
gap: `Player ${player.name} was not dispatched because the ${MAX_PLAYER_MODELS}-player cap was reached.`
}));
const initialAgentSnapshot = await list_agents({});
const runComponent = chooseUnusedRunComponent(initialAgentSnapshot.agents, "player");
async function dispatchCodexPlayer(workerTaskName, player, userRequestedModel) {
const { task_name: workerTask } = await spawn_agent({
task_name: workerTaskName,
fork_turns: "none",
...(userRequestedModel ? { model: userRequestedModel } : {}),
message: `Model ${player.name} as a self-interested actor.
PLAYER: ${player.name}
GOALS: ${player.goals}
LEVERAGE: ${player.leverage}
SITUATION: ${situation}
Return only raw JSON with exactly these keys: ${PLAYER_OUTPUT_KEYS.join(", ")}.
No Markdown fences.`
});
return { workerTask, player };
}
async function collectCodexPlayerFinals(workerTasks) {
const playerByWorkerTask = new Map(
workerTasks.map(({ workerTask, player }) => [workerTask, player])
);
const pendingWorkerTasks = new Set(workerTasks.map(({ workerTask }) => workerTask));
const resultsByWorkerTask = new Map();
const correctionRequested = new Set();
const rejectedFinalByWorkerTask = new Map();
const collectionGaps = [];
const collectionDeadline = Date.now() + COLLECTION_TIMEOUT_MS;
while (pendingWorkerTasks.size > 0 && Date.now() < collectionDeadline) {
const remainingMs = collectionDeadline - Date.now();
if (remainingMs <= 0) break;
// Codex accepts a timeout_ms of at least 10 seconds, so the final wait can overshoot by at most that amount.
const waitMs = Math.max(10_000, Math.min(60_000, remainingMs));
await wait_agent({ timeout_ms: waitMs });
const { agents } = await list_agents({});
for (const agent of agents) {
const workerTask = agent.agent_name;
if (!pendingWorkerTasks.has(workerTask)) continue;
if (
typeof agent.agent_status !== "object" ||
typeof agent.agent_status.completed !== "string"
) {
continue;
}
const rawResponse = agent.agent_status.completed;
if (rejectedFinalByWorkerTask.get(workerTask) === rawResponse) continue;
const response = validateJsonObject(rawResponse, PLAYER_SCHEMA);
if (!hasExactKeys(response, PLAYER_OUTPUT_KEYS)) {
if (correctionRequested.has(workerTask)) {
const player = playerByWorkerTask.get(workerTask);
collectionGaps.push({
workerTask,
player: player.name,
gap: `Player ${player.name} returned invalid JSON after one correction.`
});
pendingWorkerTasks.delete(workerTask);
continue;
}
if (Date.now() >= collectionDeadline) continue;
await followup_task({
target: workerTask,
message: `Return only raw JSON with exactly these keys: ${PLAYER_OUTPUT_KEYS.join(", ")}. No Markdown fences.`
});
correctionRequested.add(workerTask);
rejectedFinalByWorkerTask.set(workerTask, rawResponse);
// The next pass rechecks collectionDeadline before waiting for this correction.
continue;
}
const player = playerByWorkerTask.get(workerTask);
resultsByWorkerTask.set(workerTask, { workerTask, player, response });
pendingWorkerTasks.delete(workerTask);
}
}
for (const workerTask of pendingWorkerTasks) {
await interrupt_agent({ target: workerTask });
const player = playerByWorkerTask.get(workerTask);
collectionGaps.push({
workerTask,
player: player.name,
gap: `No valid final was delivered for player ${player.name} from canonical task ${workerTask} before the ${COLLECTION_TIMEOUT_DESCRIPTION} collection deadline.`
});
}
return { resultsByWorkerTask, collectionGaps };
}

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 100 of 100Pattern checks on the skill's files.
  • Model reviewNot runA model review is scheduled for the next rescan.

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

7-day trend score
0

Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/novusedge/palpatine/adversary/badge)](https://verified-skill.com/skills/novusedge/palpatine/adversary)

Something wrong with this skill or its page? Tell us.