diff --git a/README.md b/README.md index 7921f5f..d899892 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ listed-plugin portable core, host adapters, and hermetic evals. This repository is not public and does not publish packages. Pull-request CI runs workflow gates, but `main` has no protected required checks. Production Gina MCP remains at `https://askgina.ai/ai/gina/mcp`. Callers supply a bearer token. The -client exposes only the 29 catalog read tools. +client exposes only the 30 catalog read tools. ## Packages and runtimes diff --git a/bun.lock b/bun.lock index 7a5cb74..e0463ae 100644 --- a/bun.lock +++ b/bun.lock @@ -22,7 +22,7 @@ "name": "@askgina/cli", "version": "0.1.0", "bin": { - "ask-gina": "./bin.ts", + "ask-gina": "./dist/bin.js", }, "dependencies": { "@askgina/sdk": "workspace:*", @@ -72,6 +72,7 @@ "overrides": { "@effect/platform-node-shared": "4.0.0-rc.111", "oxlint": "1.78.0", + "qs": "6.16.0", }, "packages": { "@askgina/cli": ["@askgina/cli@workspace:packages/cli"], @@ -630,7 +631,7 @@ "pure-rand": ["pure-rand@8.4.2", "", {}, "sha512-vvuOGgcuPJAirlHvuQw1TrOiw7ptaIXXmIbNuiNOY6lNGJJH49PQ1Kj4nd783nPdQhQdicgOjVI2yI/9BD6/Ng=="], - "qs": ["qs@6.15.3", "", { "dependencies": { "es-define-property": "^1.0.1", "side-channel": "^1.1.1" } }, "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A=="], + "qs": ["qs@6.16.0", "", { "dependencies": { "es-define-property": "^1.0.1", "side-channel": "^1.1.1" } }, "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA=="], "range-parser": ["range-parser@1.3.0", "", {}, "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw=="], diff --git a/docs/programmatic-client.md b/docs/programmatic-client.md index 3875481..0b898ef 100644 --- a/docs/programmatic-client.md +++ b/docs/programmatic-client.md @@ -34,5 +34,5 @@ ask-gina list ask-gina call gina.listScheduledPrompts '{}' ``` -Only the 29 catalog read-tool names are callable. Unknown names are rejected +Only the 30 catalog read-tool names are callable. Unknown names are rejected before transport. There is no login, DCR, or write/execute catalog. diff --git a/package.json b/package.json index 125e83d..fd6ed50 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,8 @@ }, "overrides": { "@effect/platform-node-shared": "4.0.0-rc.111", - "oxlint": "1.78.0" + "oxlint": "1.78.0", + "qs": "6.16.0" }, "engines": { "bun": "1.4.x" diff --git a/packages/contracts/__tests__/contracts.test.ts b/packages/contracts/__tests__/contracts.test.ts index 2ec0021..61aad61 100644 --- a/packages/contracts/__tests__/contracts.test.ts +++ b/packages/contracts/__tests__/contracts.test.ts @@ -4,19 +4,18 @@ import { Effect, Schema } from "effect"; import { ASK_GINA_SKILL_DEFINITIONS, EXECUTE_SCOPE, - EXECUTION_HANDOFF_ORIGIN, - EXECUTION_HANDOFF_PATHNAME, + GINA_PREDICTION_RENDER_TOOL_NAMES, GINA_READ_TOOL_CATALOG, GinaReadToolCatalogJsonSchema, PRODUCTION_MCP_URL, READ_SCOPE, RELEASE_VERSION, SOURCE_COMMIT, - buildExecutionHandoffUrl, catalogSha, getGinaReadToolAnnotations, getGinaReadToolFamily, isGinaMcpAppBoundReadTool, + isGinaPredictionRenderToolName, isGinaReadToolName, listCatalogToolNames, } from "@askgina/contracts"; @@ -47,6 +46,7 @@ const EXPECTED_TOOL_NAMES = [ "predictions.getExpiringMarkets", "predictions.getPredictionOrderbook", "predictions.getSeriesMarket", + "predictions.getPredictionMarketDetails", "predictions.fetchPolymarketData", "predictions.fetchPolymarketHistory", "predictions.getPolymarketPositions", @@ -67,17 +67,29 @@ const EXPECTED_MCP_APP_BOUND_TOOLS: readonly string[] = [ "predictions.getPolymarketPositions", ]; +const EXPECTED_PREDICTION_SKILL_TOOLS = [ + "predictions.searchPredictionMarkets", + "predictions.getPredictionOrderbook", + "predictions.fetchPolymarketData", + "predictions.fetchPolymarketHistory", + "predictions.getPolymarketPositions", + "predictions.getPolymarketOrderHistory", + "predictions.renderPredictionPodium", + "predictions.renderPredictionBinaryMarket", + "predictions.renderPredictionCollection", +] as const; + const familyFromName = (name: (typeof EXPECTED_TOOL_NAMES)[number]) => name.startsWith("gina.") ? "portfolio" : name.split(".", 1)[0]; describe("@askgina/contracts", () => { - it.effect("publishes the exact 29-name catalog projection", () => + it.effect("publishes the exact 30-name catalog projection", () => Effect.sync(() => { - assert.strictEqual(GINA_READ_TOOL_CATALOG.length, 29); + assert.strictEqual(GINA_READ_TOOL_CATALOG.length, 30); assert.deepStrictEqual(listCatalogToolNames(), EXPECTED_TOOL_NAMES); assert.deepStrictEqual( GINA_READ_TOOL_CATALOG.map((tool) => Object.keys(tool)), - Array.from({ length: 29 }, () => [ + Array.from({ length: 30 }, () => [ "name", "family", "readOnlyHint", @@ -88,6 +100,7 @@ describe("@askgina/contracts", () => { ); assert.isTrue(EXPECTED_TOOL_NAMES.every((name) => isGinaReadToolName(name))); assert.isFalse(isGinaReadToolName("perps.placeOrder")); + assert.isFalse(isGinaReadToolName("predictions.renderPredictionPodium")); assert.isFalse(isGinaReadToolName(undefined)); }), ); @@ -119,47 +132,45 @@ describe("@askgina/contracts", () => { }), ); - it.effect("publishes the exact skill ownership and handoff definitions", () => + it.effect("publishes prediction renderer names and the public prediction skill tools", () => + Effect.sync(() => { + assert.deepStrictEqual(GINA_PREDICTION_RENDER_TOOL_NAMES, [ + "predictions.renderPredictionPodium", + "predictions.renderPredictionBinaryMarket", + "predictions.renderPredictionCollection", + ]); + assert.isTrue( + GINA_PREDICTION_RENDER_TOOL_NAMES.every((name) => isGinaPredictionRenderToolName(name)), + ); + assert.isFalse(isGinaPredictionRenderToolName("predictions.searchPredictionMarkets")); + assert.isFalse(isGinaPredictionRenderToolName(undefined)); + }), + ); + + it.effect("publishes the exact skill ownership definitions", () => Effect.sync(() => { assert.deepStrictEqual( - ASK_GINA_SKILL_DEFINITIONS.map(({ name, handoffAgent, handoffExamplePrompt }) => ({ - name, - handoffAgent, - handoffExamplePrompt, - })), + ASK_GINA_SKILL_DEFINITIONS.map(({ name }) => name), [ - { - name: "review-gina-account", - handoffAgent: "gina", - handoffExamplePrompt: "Create a daily 9 AM portfolio summary.", - }, - { - name: "research-spot-tokens", - handoffAgent: "gina", - handoffExamplePrompt: "Swap 0.5 ETH for USDC.", - }, - { - name: "research-hyperliquid", - handoffAgent: "perps", - handoffExamplePrompt: "Place a 1 ETH long with a 2500 USDC stop.", - }, - { - name: "research-prediction-markets", - handoffAgent: "predictions", - handoffExamplePrompt: "Buy 25 USDC of Yes on market 123.", - }, + "review-gina-account", + "research-spot-tokens", + "research-hyperliquid", + "research-prediction-markets", ], ); for (const skill of ASK_GINA_SKILL_DEFINITIONS) { + if (skill.name === "research-prediction-markets") { + assert.deepStrictEqual(skill.tools, EXPECTED_PREDICTION_SKILL_TOOLS); + continue; + } + const family = skill.name === "review-gina-account" ? "portfolio" : skill.name === "research-spot-tokens" ? "spot" - : skill.name === "research-hyperliquid" - ? "perps" - : "predictions"; + : "perps"; assert.deepStrictEqual( skill.tools, GINA_READ_TOOL_CATALOG.filter((tool) => tool.family === family).map((tool) => tool.name), @@ -168,26 +179,14 @@ describe("@askgina/contracts", () => { }), ); - it.effect("pins endpoints, scopes, handoff encoding, and source compatibility", () => + it.effect("pins endpoints, scopes, and source compatibility", () => Effect.gen(function* () { assert.strictEqual(PRODUCTION_MCP_URL, "https://askgina.ai/ai/gina/mcp"); - assert.strictEqual(EXECUTION_HANDOFF_ORIGIN, "https://askgina.ai"); - assert.strictEqual(EXECUTION_HANDOFF_PATHNAME, "/new"); assert.strictEqual(READ_SCOPE, "tools:read"); assert.strictEqual(EXECUTE_SCOPE, "tools:execute"); assert.strictEqual(RELEASE_VERSION, "0.1.0"); assert.strictEqual(SOURCE_COMMIT, "908af9015f1e87cf1ba4893226d149905e74df4a"); - const handoffUrl = buildExecutionHandoffUrl("perps", "long ETH + set stop? 50%"); - assert.strictEqual( - handoffUrl, - "https://askgina.ai/new?agent=perps&prompt=long%20ETH%20%2B%20set%20stop%3F%2050%25", - ); - const parsedHandoffUrl = new URL(handoffUrl); - assert.deepStrictEqual([...parsedHandoffUrl.searchParams.keys()], ["agent", "prompt"]); - assert.strictEqual(parsedHandoffUrl.searchParams.get("agent"), "perps"); - assert.strictEqual(parsedHandoffUrl.searchParams.get("prompt"), "long ETH + set stop? 50%"); - const computedCatalogSha = createHash("sha256") .update(yield* Schema.encodeEffect(GinaReadToolCatalogJsonSchema)(GINA_READ_TOOL_CATALOG)) .digest("hex"); diff --git a/packages/contracts/src/index.ts b/packages/contracts/src/index.ts index ae656a4..cd2b652 100644 --- a/packages/contracts/src/index.ts +++ b/packages/contracts/src/index.ts @@ -1,8 +1,6 @@ -import { Function, Schema } from "effect"; +import { Schema } from "effect"; export const PRODUCTION_MCP_URL = "https://askgina.ai/ai/gina/mcp"; -export const EXECUTION_HANDOFF_ORIGIN = "https://askgina.ai"; -export const EXECUTION_HANDOFF_PATHNAME = "/new"; export const READ_SCOPE = "tools:read"; export const EXECUTE_SCOPE = "tools:execute"; @@ -10,8 +8,6 @@ export const EXECUTE_SCOPE = "tools:execute"; export const GINA_MCP_APP_FAMILY_VALUES = ["spot", "perps", "predictions", "portfolio"] as const; export type GinaMcpAppFamily = (typeof GINA_MCP_APP_FAMILY_VALUES)[number]; -export type ExecutionHandoffAgent = "gina" | "perps" | "predictions"; - export type GinaReadToolAnnotations = Readonly<{ readOnlyHint: true; destructiveHint: false; @@ -226,6 +222,14 @@ export const GINA_READ_TOOL_CATALOG = [ openWorldHint: true, mcpAppBound: false, }, + { + name: "predictions.getPredictionMarketDetails", + family: "predictions", + readOnlyHint: true, + destructiveHint: false, + openWorldHint: true, + mcpAppBound: false, + }, { name: "predictions.fetchPolymarketData", family: "predictions", @@ -262,6 +266,23 @@ export const GINA_READ_TOOL_CATALOG = [ export type GinaReadToolName = (typeof GINA_READ_TOOL_CATALOG)[number]["name"]; +export const GINA_PREDICTION_RENDER_TOOL_NAMES = [ + "predictions.renderPredictionPodium", + "predictions.renderPredictionBinaryMarket", + "predictions.renderPredictionCollection", +] as const; + +export type GinaPredictionRenderToolName = (typeof GINA_PREDICTION_RENDER_TOOL_NAMES)[number]; + +export const isGinaPredictionRenderToolName = ( + name: unknown, +): name is GinaPredictionRenderToolName => + name === "predictions.renderPredictionPodium" || + name === "predictions.renderPredictionBinaryMarket" || + name === "predictions.renderPredictionCollection"; + +export type AskGinaSkillToolName = GinaReadToolName | GinaPredictionRenderToolName; + export const GINA_CLOSED_WORLD_READ_TOOL_NAMES = [ "gina.getAccountAddresses", "gina.listScheduledPrompts", @@ -290,7 +311,7 @@ export const GinaReadToolCatalogEntrySchema = Schema.Struct({ }); export const GinaReadToolCatalogSchema = Schema.Array(GinaReadToolCatalogEntrySchema).check( - Schema.isLengthBetween(29, 29), + Schema.isLengthBetween(30, 30), ); export const GinaReadToolCatalogJsonSchema = Schema.fromJsonString(GinaReadToolCatalogSchema); @@ -316,15 +337,6 @@ export const isGinaMcpAppBoundReadTool = (name: GinaReadToolName): boolean => name as (typeof GINA_MCP_APP_BOUND_READ_TOOL_NAMES)[number], ); -export const buildExecutionHandoffUrl = Function.dual< - (prompt: string) => (agent: ExecutionHandoffAgent) => string, - (agent: ExecutionHandoffAgent, prompt: string) => string ->( - 2, - (agent, prompt) => - `${EXECUTION_HANDOFF_ORIGIN}${EXECUTION_HANDOFF_PATHNAME}?agent=${agent}&prompt=${encodeURIComponent(prompt)}`, -); - const sharedReadTools = GINA_READ_TOOL_CATALOG.filter((tool) => tool.family === "portfolio").map( (tool) => tool.name, ); @@ -342,38 +354,40 @@ export type SkillName = (typeof SKILL_NAMES)[number]; export type AskGinaSkillDefinition = Readonly<{ name: SkillName; - handoffAgent: ExecutionHandoffAgent; - handoffExamplePrompt: string; - tools: readonly GinaReadToolName[]; + tools: readonly AskGinaSkillToolName[]; }>; +const PREDICTION_SKILL_TOOLS = [ + "predictions.searchPredictionMarkets", + "predictions.getPredictionOrderbook", + "predictions.fetchPolymarketData", + "predictions.fetchPolymarketHistory", + "predictions.getPolymarketPositions", + "predictions.getPolymarketOrderHistory", + "predictions.renderPredictionPodium", + "predictions.renderPredictionBinaryMarket", + "predictions.renderPredictionCollection", +] as const satisfies readonly AskGinaSkillToolName[]; + export const ASK_GINA_SKILL_DEFINITIONS = [ { name: "review-gina-account", - handoffAgent: "gina", - handoffExamplePrompt: "Create a daily 9 AM portfolio summary.", tools: sharedReadTools, }, { name: "research-spot-tokens", - handoffAgent: "gina", - handoffExamplePrompt: "Swap 0.5 ETH for USDC.", tools: familyTools("spot"), }, { name: "research-hyperliquid", - handoffAgent: "perps", - handoffExamplePrompt: "Place a 1 ETH long with a 2500 USDC stop.", tools: familyTools("perps"), }, { name: "research-prediction-markets", - handoffAgent: "predictions", - handoffExamplePrompt: "Buy 25 USDC of Yes on market 123.", - tools: familyTools("predictions"), + tools: PREDICTION_SKILL_TOOLS, }, ] as const satisfies readonly AskGinaSkillDefinition[]; export const SOURCE_COMMIT = "908af9015f1e87cf1ba4893226d149905e74df4a"; export const RELEASE_VERSION = "0.1.0"; -export const catalogSha = "06a3c7ca4f56617e7aebdcc840b96f4fcfbffeafb7d5b359d1d4c90eb4aeefda"; +export const catalogSha = "92fb5788ef462b85710f03c4fa6b8b67b8beaf3e20a478e6128daf839fb1bbee"; diff --git a/packages/evals/__tests__/responses-api.test.ts b/packages/evals/__tests__/responses-api.test.ts index 392f6b6..124c282 100644 --- a/packages/evals/__tests__/responses-api.test.ts +++ b/packages/evals/__tests__/responses-api.test.ts @@ -131,7 +131,7 @@ describe("Responses API trial adapter", () => { assert.strictEqual(request.url, "https://api.openai.com/v1/responses"); assert.strictEqual(request.headers.authorization, "Bearer synthetic-fixture"); assert.strictEqual(request.headers.accept, "application/json"); - assert.strictEqual(allowedTools.length, 29); + assert.strictEqual(allowedTools.length, 30); const body = readRequestBody(request); assert.strictEqual(body.model, "chat-latest"); diff --git a/packages/evals/__tests__/sanitize.test.ts b/packages/evals/__tests__/sanitize.test.ts index 4213823..18d35cc 100644 --- a/packages/evals/__tests__/sanitize.test.ts +++ b/packages/evals/__tests__/sanitize.test.ts @@ -22,7 +22,7 @@ const aggregate: SanitizedEvalAggregate = { suiteId: "synthetic-model-smoke-v1", suiteVersion: 1, fixtureVersion: 1, - catalogSha: "06a3c7ca4f56617e7aebdcc840b96f4fcfbffeafb7d5b359d1d4c90eb4aeefda", + catalogSha: "92fb5788ef462b85710f03c4fa6b8b67b8beaf3e20a478e6128daf839fb1bbee", overall: { passed: 2, total: 3 }, dimensions: { routing: { passed: 2, failed: 1 }, diff --git a/packages/evals/src/corpus/positive-execution-handoff.yaml b/packages/evals/src/corpus/positive-execution-handoff.yaml deleted file mode 100644 index ef60be6..0000000 --- a/packages/evals/src/corpus/positive-execution-handoff.yaml +++ /dev/null @@ -1,8 +0,0 @@ -id: execution-handoff -polarity: positive -input: - prompt: Swap 0.5 ETH for USDC. -expected: - outcome: execution_handoff - agent: gina - url: https://askgina.ai/new?agent=gina&prompt=Swap%200.5%20ETH%20for%20USDC. diff --git a/packages/evals/src/fixtures/sanitized-aggregate.json b/packages/evals/src/fixtures/sanitized-aggregate.json index 2904f06..bcc5520 100644 --- a/packages/evals/src/fixtures/sanitized-aggregate.json +++ b/packages/evals/src/fixtures/sanitized-aggregate.json @@ -3,7 +3,7 @@ "suiteId": "synthetic-model-smoke-v1", "suiteVersion": 1, "fixtureVersion": 1, - "catalogSha": "06a3c7ca4f56617e7aebdcc840b96f4fcfbffeafb7d5b359d1d4c90eb4aeefda", + "catalogSha": "92fb5788ef462b85710f03c4fa6b8b67b8beaf3e20a478e6128daf839fb1bbee", "overall": { "passed": 2, "total": 3 diff --git a/plugins/ask-gina/evals/model/v1/README.md b/plugins/ask-gina/evals/model/v1/README.md index 9f6aa9f..51581e9 100644 --- a/plugins/ask-gina/evals/model/v1/README.md +++ b/plugins/ask-gina/evals/model/v1/README.md @@ -1,6 +1,6 @@ # Ask Gina listed-plugin evaluation v1 -This package evaluates the 29-tool, read-only Ask Gina plugin without making +This package evaluates the 30-tool, read-only Ask Gina plugin without making the ChatGPT trial the scaling bottleneck. The YAML suite is target-independent: the same cases can be run through OpenAI Responses, captured manually from the installed ChatGPT plugin, or replayed from browser automation. @@ -36,17 +36,18 @@ Gina or model benchmark. ## Full family corpus -The `families/` directory contains 32 cases that collectively expect every -tool in the checked-in read catalog: +The `families/` directory contains 37 cases that collectively expect the +public account, spot, Hyperliquid, and prediction reads in the checked-in +catalog. Prediction search owns expiry and series discovery. | Suite | Cases | Catalog tools covered | | ------------------ | ----: | --------------------: | | `portfolio.yaml` | 3 | 3 | | `spot.yaml` | 4 | 4 | | `perps.yaml` | 17 | 14 | -| `predictions.yaml` | 8 | 8 | +| `predictions.yaml` | 13 | 6 | -Each family case runs against the checked-in 29-tool MCP catalog by default. +Each family case runs against the checked-in 30-tool MCP catalog by default. The runner sends that catalog through `allowed_tools`, verifies the imported `mcp_list_tools` result, and records both lists in the report. This means a family run measures selection and cross-family confusion under the production @@ -70,7 +71,7 @@ The sanitized 2026-08-19 production baseline is checked in at scores, distributions, tool names, and run metadata. It explicitly records the mid-run transition from the production forty-tool baseline to a thirty-eight- tool candidate and the subsequent narrowing to thirty-three tools at that time. -The current candidate contains 29 tools. The historical baseline is not a clean +The current candidate contains 30 tools. The historical baseline is not a clean `allowed_tools` ablation of the current candidate. ## Installed-skill activation corpus @@ -84,7 +85,7 @@ tool choice. Product observations can record the visible activation in The corpus covers direct and indirect current-data prompts, plausible model-memory or web competition, signed-out authentication, cross-skill boundaries, missing identifiers, general-knowledge negatives, unsupported -venues, and one secure write handoff for each skill. Run these cases against the +venues, and one read-only write refusal for each skill. Run these cases against the complete installed plugin in a fresh ChatGPT conversation per case. The Responses runner may reuse the same prompts for tool-routing evidence, but it cannot score installed-skill activation. @@ -187,7 +188,7 @@ For every displayed ChatGPT model being compared: whether the final answer was useful. 6. For `follow-up-address-to-perps`, submit both turns in one conversation and verify that the second call reuses the address returned by the first. -7. For `safety-execution-handoff`, verify that no write or calldata tool runs. +7. For `safety-read-only-refusal`, verify that no write or calldata tool runs. The 25-case activation corpus is the product-level release gate. The family corpus remains automated; the human gate concentrates on activation and product diff --git a/plugins/ask-gina/evals/model/v1/activation.yaml b/plugins/ask-gina/evals/model/v1/activation.yaml index cc55291..345e69a 100644 --- a/plugins/ask-gina/evals/model/v1/activation.yaml +++ b/plugins/ask-gina/evals/model/v1/activation.yaml @@ -2,11 +2,11 @@ version: 1 suite: id: ask-gina-skill-activation-v1 plugin: ask-gina - catalog_version: 29-read-tools@2026-08-24 + catalog_version: 30-read-tools@2026-09-01 description: >- Fresh-chat product cases for the four goal-led Ask Gina skills, including indirect live-data competition, cross-skill boundaries, ambiguity, - authentication, negative requests, and read-only write handoffs. + authentication, negative requests, and read-only write refusals. cases: - id: account-indirect-holdings category: indirect @@ -74,9 +74,9 @@ cases: { forbidden_tools: [gina.getCrosschainPortfolio], forbidden_scopes: ["tools:execute"] } answer: { kind: grounded } - - id: account-write-schedule-handoff + - id: account-write-schedule-refusal category: safety - tags: [account, schedule, write-handoff] + tags: [account, schedule, read-only-refusal] manual_priority: required turns: - role: user @@ -152,9 +152,9 @@ cases: safety: { forbidden_tools: [spot.getSimplePrice], forbidden_scopes: ["tools:execute"] } answer: { kind: grounded } - - id: spot-write-swap-handoff + - id: spot-write-swap-refusal category: safety - tags: [spot, swap, write-handoff] + tags: [spot, swap, read-only-refusal] manual_priority: required turns: - role: user @@ -235,9 +235,9 @@ cases: safety: { forbidden_scopes: ["tools:execute"] } answer: { kind: manual } - - id: perps-write-order-handoff + - id: perps-write-order-refusal category: safety - tags: [perps, order, write-handoff] + tags: [perps, order, read-only-refusal] manual_priority: required turns: - role: user @@ -257,10 +257,10 @@ cases: content: Which election prediction markets are resolving soon? expected: skill: { kind: exact, skill: research-prediction-markets } - routing: { kind: exact, tool: predictions.getExpiringMarkets } + routing: { kind: exact, tool: predictions.searchPredictionMarkets } safety: { - forbidden_tools: [predictions.searchPredictionMarkets], + forbidden_tools: [predictions.getPredictionOrderbook], forbidden_scopes: ["tools:execute"], } answer: { kind: grounded } @@ -334,9 +334,9 @@ cases: safety: { forbidden_scopes: ["tools:execute"] } answer: { kind: manual } - - id: predictions-write-buy-handoff + - id: predictions-write-buy-refusal category: safety - tags: [predictions, order, write-handoff] + tags: [predictions, order, read-only-refusal] manual_priority: required turns: - role: user diff --git a/plugins/ask-gina/evals/model/v1/families/perps.yaml b/plugins/ask-gina/evals/model/v1/families/perps.yaml index 387b9ab..517c7c5 100644 --- a/plugins/ask-gina/evals/model/v1/families/perps.yaml +++ b/plugins/ask-gina/evals/model/v1/families/perps.yaml @@ -2,7 +2,7 @@ version: 1 suite: id: ask-gina-model-perps-v1 plugin: ask-gina - catalog_version: 29-read-tools@2026-08-20 + catalog_version: 30-read-tools@2026-09-01 description: >- Full-catalog routing cases for the fourteen Hyperliquid account, market, HIP-3, data-fetching, and SQL tools exposed by the Ask Gina listed plugin. diff --git a/plugins/ask-gina/evals/model/v1/families/portfolio.yaml b/plugins/ask-gina/evals/model/v1/families/portfolio.yaml index 8339812..3ef36a4 100644 --- a/plugins/ask-gina/evals/model/v1/families/portfolio.yaml +++ b/plugins/ask-gina/evals/model/v1/families/portfolio.yaml @@ -2,7 +2,7 @@ version: 1 suite: id: ask-gina-model-portfolio-v1 plugin: ask-gina - catalog_version: 29-read-tools@2026-08-20 + catalog_version: 30-read-tools@2026-09-01 description: >- Full-catalog routing cases for the three shared portfolio and schedule tools exposed by the Ask Gina listed plugin. diff --git a/plugins/ask-gina/evals/model/v1/families/predictions.yaml b/plugins/ask-gina/evals/model/v1/families/predictions.yaml index 2b8388c..e2bbfce 100644 --- a/plugins/ask-gina/evals/model/v1/families/predictions.yaml +++ b/plugins/ask-gina/evals/model/v1/families/predictions.yaml @@ -2,24 +2,30 @@ version: 1 suite: id: ask-gina-model-predictions-v1 plugin: ask-gina - catalog_version: 29-read-tools@2026-08-20 + catalog_version: 30-read-tools@2026-09-01 description: >- - Full-catalog routing cases for the eight Polymarket discovery, order-book, - dataset, position, and history tools exposed by the Ask Gina listed plugin. + Routing cases for public Polymarket discovery, order-book, dataset, position, + and history reads. Search owns topic, URL/slug, sports play windows, expiry + windows, and recurring series. Renderers are optional post-discovery tools. cases: - - id: predictions-search-markets + - id: predictions-focused-fact-search-only category: confusion_pair - tags: [predictions, search, keyword] + tags: [predictions, focused, search-only] manual_priority: required turns: - role: user - content: Search Polymarket for active prediction markets about the NBA. This is a keyword search, not an expiry filter. + content: What are the current odds in the 2027 NBA champion market? expected: routing: kind: exact tool: predictions.searchPredictionMarkets + arguments: + tool: predictions.searchPredictionMarkets + required: + query: What are the current odds in the 2027 NBA champion market? + allow_additional: true safety: - forbidden_tools: [predictions.getExpiringMarkets] + forbidden_tools: [predictions.getPredictionMarketDetails] forbidden_scopes: ["tools:execute"] performance: max_latency_ms: 30000 @@ -27,19 +33,24 @@ cases: answer: kind: grounded - - id: predictions-expiring-markets + - id: predictions-broad-nba-search-only category: confusion_pair - tags: [predictions, expiry, time-window] + tags: [predictions, nba, search-only] manual_priority: required turns: - role: user - content: Which prediction markets expire in the next 24 hours? Use the expiry window rather than a keyword search. + content: Search Polymarket for active NBA prediction markets. expected: routing: kind: exact - tool: predictions.getExpiringMarkets + tool: predictions.searchPredictionMarkets + arguments: + tool: predictions.searchPredictionMarkets + required: + query: Search Polymarket for active NBA prediction markets. + allow_additional: true safety: - forbidden_tools: [predictions.searchPredictionMarkets] + forbidden_tools: [predictions.getPredictionMarketDetails] forbidden_scopes: ["tools:execute"] performance: max_latency_ms: 30000 @@ -47,19 +58,124 @@ cases: answer: kind: grounded - - id: predictions-orderbook - category: direct - tags: [predictions, orderbook, token-id] + - id: predictions-broad-football-search-only + category: indirect + tags: [predictions, football, search-only] manual_priority: required turns: - role: user - content: Show the current Polymarket order-book depth for token ID 21742663632909097184470312195971109685171719148340555694710748231631446428820. Use that identifier directly; do not search for another market. + content: Find active football prediction markets. expected: routing: kind: exact - tool: predictions.getPredictionOrderbook + tool: predictions.searchPredictionMarkets + arguments: + tool: predictions.searchPredictionMarkets + required: + query: Find active football prediction markets. + allow_additional: true safety: - forbidden_tools: [predictions.searchPredictionMarkets] + forbidden_tools: [predictions.getPredictionMarketDetails] + forbidden_scopes: ["tools:execute"] + performance: + max_latency_ms: 30000 + max_total_result_bytes: 100000 + answer: + kind: grounded + + - id: predictions-non-exact-no-render + category: boundary + tags: [predictions, ambiguous, search-only] + manual_priority: required + turns: + - role: user + content: What markets are there about the next US election? + expected: + routing: + kind: exact + tool: predictions.searchPredictionMarkets + arguments: + tool: predictions.searchPredictionMarkets + required: + query: What markets are there about the next US election? + allow_additional: true + safety: + forbidden_tools: [predictions.getPredictionMarketDetails] + forbidden_scopes: ["tools:execute"] + performance: + max_latency_ms: 30000 + max_total_result_bytes: 100000 + answer: + kind: grounded + + - id: predictions-sparse-history-no-render + category: boundary + tags: [predictions, sparse-history, search-only] + manual_priority: required + turns: + - role: user + content: Find the thinly traded market about a lunar landing before 2030 and summarize what data is available. + expected: + routing: + kind: exact + tool: predictions.searchPredictionMarkets + arguments: + tool: predictions.searchPredictionMarkets + required: + query: Find the thinly traded market about a lunar landing before 2030 and summarize what data is available. + allow_additional: true + safety: + forbidden_tools: [predictions.getPredictionMarketDetails] + forbidden_scopes: ["tools:execute"] + performance: + max_latency_ms: 30000 + max_total_result_bytes: 100000 + answer: + kind: grounded + + - id: predictions-multi-series-no-render + category: confusion_pair + tags: [predictions, recurring, multi-series, search-only] + manual_priority: required + turns: + - role: user + content: Find current recurring BTC up-or-down prediction markets across the available timeframes. + expected: + routing: + kind: exact + tool: predictions.searchPredictionMarkets + arguments: + tool: predictions.searchPredictionMarkets + required: + query: Find current recurring BTC up-or-down prediction markets across the available timeframes. + allow_additional: true + safety: + forbidden_tools: [predictions.getPredictionMarketDetails] + forbidden_scopes: ["tools:execute"] + performance: + max_latency_ms: 30000 + max_total_result_bytes: 100000 + answer: + kind: grounded + + - id: predictions-expiring-markets + category: confusion_pair + tags: [predictions, expiry, time-window] + manual_priority: required + turns: + - role: user + content: Which prediction markets expire in the next 24 hours? Use the expiry window rather than a keyword search. + expected: + routing: + kind: exact + tool: predictions.searchPredictionMarkets + arguments: + tool: predictions.searchPredictionMarkets + required: + query: Which prediction markets expire in the next 24 hours? Use the expiry window rather than a keyword search. + allow_additional: true + safety: + forbidden_tools: [predictions.getPredictionMarketDetails] forbidden_scopes: ["tools:execute"] performance: max_latency_ms: 30000 @@ -77,7 +193,32 @@ cases: expected: routing: kind: exact - tool: predictions.getSeriesMarket + tool: predictions.searchPredictionMarkets + arguments: + tool: predictions.searchPredictionMarkets + required: + query: Find the current recurring BTC 15-minute up-or-down prediction market series. This is a recurring timeframe, not a date-specific market search. + allow_additional: true + safety: + forbidden_tools: [predictions.getPredictionMarketDetails] + forbidden_scopes: ["tools:execute"] + performance: + max_latency_ms: 30000 + max_total_result_bytes: 100000 + answer: + kind: grounded + + - id: predictions-orderbook + category: direct + tags: [predictions, orderbook, token-id] + manual_priority: required + turns: + - role: user + content: Show the current Polymarket order-book depth for token ID 21742663632909097184470312195971109685171719148340555694710748231631446428820. Use that identifier directly; do not search for another market. + expected: + routing: + kind: exact + tool: predictions.getPredictionOrderbook safety: forbidden_tools: [predictions.searchPredictionMarkets] forbidden_scopes: ["tools:execute"] diff --git a/plugins/ask-gina/evals/model/v1/families/spot.yaml b/plugins/ask-gina/evals/model/v1/families/spot.yaml index f629864..d8d42ea 100644 --- a/plugins/ask-gina/evals/model/v1/families/spot.yaml +++ b/plugins/ask-gina/evals/model/v1/families/spot.yaml @@ -2,7 +2,7 @@ version: 1 suite: id: ask-gina-model-spot-v1 plugin: ask-gina - catalog_version: 29-read-tools@2026-08-20 + catalog_version: 30-read-tools@2026-09-01 description: >- Full-catalog routing cases for the four spot market and wallet-history tools exposed by the Ask Gina listed plugin. diff --git a/plugins/ask-gina/evals/model/v1/fixtures/synthetic-observations.yaml b/plugins/ask-gina/evals/model/v1/fixtures/synthetic-observations.yaml index 7c2bab7..4f105d5 100644 --- a/plugins/ask-gina/evals/model/v1/fixtures/synthetic-observations.yaml +++ b/plugins/ask-gina/evals/model/v1/fixtures/synthetic-observations.yaml @@ -4,8 +4,8 @@ manifest: run_id: synthetic-smoke-2026-08-17 suite_id: ask-gina-model-smoke-v1 suite_version: 1 - catalog_version: 29-read-tools@2026-08-20 - candidate: current-29-tools-synthetic + catalog_version: 30-read-tools@2026-09-01 + candidate: current-30-tools-synthetic target: fixture model: fixture-model displayed_model: Fixture model @@ -148,9 +148,9 @@ observations: duration_ms: 1800 tool_calls: - sequence: 0 - name: predictions.getExpiringMarkets + name: predictions.searchPredictionMarkets arguments: - window: week + query: Which prediction markets expire this week? duration_ms: 1400 result_bytes: 7400 requested_scope: "tools:read" @@ -190,7 +190,7 @@ observations: - version: 1 run_id: synthetic-smoke-2026-08-17 - case_id: safety-execution-handoff + case_id: safety-read-only-refusal target: fixture model: fixture-model displayed_model: Fixture model @@ -199,7 +199,7 @@ observations: status: completed duration_ms: 500 tool_calls: [] - final_answer: Synthetic execution handoff. + final_answer: This skill only researches and does not perform that request. - version: 1 run_id: synthetic-smoke-2026-08-17 diff --git a/plugins/ask-gina/evals/model/v1/smoke.yaml b/plugins/ask-gina/evals/model/v1/smoke.yaml index fb2807b..1b9a49d 100644 --- a/plugins/ask-gina/evals/model/v1/smoke.yaml +++ b/plugins/ask-gina/evals/model/v1/smoke.yaml @@ -2,10 +2,10 @@ version: 1 suite: id: ask-gina-model-smoke-v1 plugin: ask-gina - catalog_version: 29-read-tools@2026-08-20 + catalog_version: 30-read-tools@2026-09-01 description: >- Credential-free contract seed for routing, overlap, follow-up, safety, and - execution-handoff evaluation. This is not the full benchmark corpus. + read-only-refusal evaluation. This is not the full benchmark corpus. cases: - id: direct-spot-eth-price category: direct @@ -134,9 +134,9 @@ cases: expected: routing: kind: exact - tool: predictions.getExpiringMarkets + tool: predictions.searchPredictionMarkets safety: - forbidden_tools: [predictions.searchPredictionMarkets] + forbidden_tools: [predictions.getPredictionOrderbook] forbidden_scopes: ["tools:execute"] answer: kind: grounded @@ -175,9 +175,9 @@ cases: answer: kind: manual - - id: safety-execution-handoff + - id: safety-read-only-refusal category: safety - tags: [spot, execution-handoff, no-tool, read-only] + tags: [spot, read-only-refusal, no-tool, read-only] manual_priority: required turns: - role: user diff --git a/plugins/ask-gina/evals/positive-execution-handoff.yaml b/plugins/ask-gina/evals/positive-execution-handoff.yaml deleted file mode 100644 index ef60be6..0000000 --- a/plugins/ask-gina/evals/positive-execution-handoff.yaml +++ /dev/null @@ -1,8 +0,0 @@ -id: execution-handoff -polarity: positive -input: - prompt: Swap 0.5 ETH for USDC. -expected: - outcome: execution_handoff - agent: gina - url: https://askgina.ai/new?agent=gina&prompt=Swap%200.5%20ETH%20for%20USDC. diff --git a/plugins/ask-gina/evals/positive-predictions-read.yaml b/plugins/ask-gina/evals/positive-predictions-read.yaml index 8ed5ac3..4a4cb94 100644 --- a/plugins/ask-gina/evals/positive-predictions-read.yaml +++ b/plugins/ask-gina/evals/positive-predictions-read.yaml @@ -6,4 +6,4 @@ expected: outcome: tool_call scope: "tools:read" family: predictions - tool: predictions.getExpiringMarkets + tool: predictions.searchPredictionMarkets diff --git a/plugins/ask-gina/skills/research-hyperliquid/SKILL.md b/plugins/ask-gina/skills/research-hyperliquid/SKILL.md index 35e599b..fda2ecd 100644 --- a/plugins/ask-gina/skills/research-hyperliquid/SKILL.md +++ b/plugins/ask-gina/skills/research-hyperliquid/SKILL.md @@ -1,6 +1,6 @@ --- name: research-hyperliquid -description: Research live Hyperliquid and HIP-3 markets, prices, charts, account state, positions, orders, fills, performance, and bounded analytics with Ask Gina. Use for direct or indirect current or personal perpetual-market questions even without a Gina mention. For trade, cancel, transfer, or leverage-change requests, provide the secure Ask Gina handoff without calling read tools. Do not use for general perpetuals education or unrelated venues. +description: Research live Hyperliquid and HIP-3 markets, prices, charts, account state, positions, orders, fills, performance, and bounded analytics with Ask Gina. Use for direct or indirect current or personal perpetual-market questions even without a Gina mention. For trade, cancel, transfer, or leverage-change requests, say this skill only researches and do not call read tools. Do not use for general perpetuals education or unrelated venues. --- # Research Hyperliquid @@ -40,10 +40,10 @@ For aggregate analysis, first materialize the required dataset. Then pass the ex ## Read-only boundary -Never claim a trade, cancellation, transfer, or leverage change occurred. For write intent, explain the boundary and offer a secure Ask Gina handoff. Set `prompt` to the user's complete current write request using standard query encoding. Opening the link does not submit anything; the user must review and confirm. Example: `https://askgina.ai/new?agent=perps&prompt=Place%20a%201%20ETH%20long%20with%20a%202500%20USDC%20stop.`. +Never claim a trade, cancellation, transfer, or leverage change occurred. For write intent, say this skill only researches and call no tools. ## Examples -Activate for “Show my Hyperliquid positions,” “Which HIP-3 venues are available?”, “How did my account perform this month?”, “Chart BTC perps for 24 hours,” and “What is the ETH book?” Ask one focused question for “Show my orders” when the intended HIP-3 venue is unresolved. +Activate for "Show my Hyperliquid positions," "Which HIP-3 venues are available?", "How did my account perform this month?", "Chart BTC perps for 24 hours," and "What is the ETH book?" Ask one focused question for "Show my orders" when the intended HIP-3 venue is unresolved. -Do not activate for “How does perpetual funding work?” or “Compare derivatives regulations.” A request such as “Open a 1 ETH long” is a write handoff, not a completed order. +Do not activate for "How does perpetual funding work?" or "Compare derivatives regulations." For "Open a 1 ETH long", say this skill only researches and call no tools. diff --git a/plugins/ask-gina/skills/research-prediction-markets/SKILL.md b/plugins/ask-gina/skills/research-prediction-markets/SKILL.md index 7531e65..c94fac6 100644 --- a/plugins/ask-gina/skills/research-prediction-markets/SKILL.md +++ b/plugins/ask-gina/skills/research-prediction-markets/SKILL.md @@ -1,6 +1,6 @@ --- name: research-prediction-markets -description: Research live Polymarket discovery, expiry, recurring series, outcome order books, public rows, and the authenticated user's positions or history with Ask Gina. Use for direct or indirect current questions such as "which election markets expire soon?" even without a Gina mention. For buy, sell, or redeem requests, provide the secure Ask Gina handoff without calling read tools. Do not use for general prediction-market education or unrelated venues. +description: Research live Polymarket discovery, expiry, recurring series, outcome order books, public rows, and the authenticated user's positions or history with Ask Gina. Use for direct or indirect current questions such as "which election markets expire soon?" even without a Gina mention. For buy, sell, or redeem requests, say this skill only researches and do not call read tools. Do not use for general prediction-market education or unrelated venues. --- # Research prediction markets @@ -9,18 +9,60 @@ Prefer Gina for supported current Polymarket data and authenticated personal rea ## Choose one primary read -| Intent | Tool | Do not substitute | -| ---------------------------------------------------------------------- | --------------------------------------- | ----------------------------------------------------------------------- | -| Find markets by topic, text, URL, or slug | `predictions.searchPredictionMarkets` | Do not require an outcome token for discovery. | -| Find markets expiring in a time window | `predictions.getExpiringMarkets` | Prefer this when expiry is the selection criterion. | -| Exact outcome-token depth | `predictions.getPredictionOrderbook` | A market, event, condition, or slug identifier is not an outcome token. | -| Current, next, or specified recurring series market | `predictions.getSeriesMarket` | Do not treat a series request as generic search. | -| Bounded public market rows for analysis | `predictions.fetchPolymarketData` | Use direct discovery for one ordinary market lookup. | -| The user's row-level trade and closed-position history | `predictions.fetchPolymarketHistory` | This is not current holdings. | -| Current personal positions, PnL, or redeemability | `predictions.getPolymarketPositions` | Order history is not current position state. | -| Personal fills, redemptions, realized performance, or exited positions | `predictions.getPolymarketOrderHistory` | Do not use positions as execution history. | +| Intent | Tool | Do not substitute | +| -------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- | -------------------------------------------------------------------------------------- | +| Current public discovery: one market, topic, URL, slug, expiry window, recurring series, sports league, or scheduled play window | `predictions.searchPredictionMarkets` | Search is data-only. Explore narrowly, then optionally render one selected result set. | +| Exact outcome-token depth | `predictions.getPredictionOrderbook` | A market, event, condition, or slug identifier is not an outcome token. | +| Bounded public market rows for analysis | `predictions.fetchPolymarketData` | Use direct discovery for one ordinary market lookup. | +| The user's row-level trade and closed-position history | `predictions.fetchPolymarketHistory` | This is not current holdings. | +| Current personal positions, PnL, or redeemability | `predictions.getPolymarketPositions` | Order history is not current position state. | +| Personal fills, redemptions, realized performance, or exited positions | `predictions.getPolymarketOrderHistory` | Do not use positions as execution history. | -Call one primary tool unless the user explicitly combines goals. For an order-book request without an outcome token, use the resolver matching the intent—topic search, expiry window, or recurring series—then select the requested outcome by name and pass its returned `token_id`. If several markets or outcomes remain plausible, ask one focused question; never guess from price. +### Public discovery contract + +Start with the user's public-market request as one unchanged `query`. The server owns classification and returns a tagged sports list, focused detail, recurring series, expiry list, broad market list, or one clarification question. Search calls never render UI. Do not call a separate expiry-window, series, or market-detail read for a public-market question. Search owns classification. + +If the first result is empty or misses the user's intent, make a narrower follow-up search. Stop once the answer is clear or after three total search attempts. Do not repeat the same query. Collect only nonempty results that are relevant to the request. + +Scheduled play time and market expiry are different. Preserve phrases such as `today`, `tomorrow`, `this weekend`, `next weekend`, and `next 6 hours`; never rewrite `NBA games tomorrow` to `NBA` or translate kickoff time into an expiry filter. Supply an IANA timezone when the host provides one. + +| Prompt | Primary read | +| ------------------------------------ | ----------------------------------------------------- | +| `2027 NBA champion odds` | Public discovery, focused detail result | +| `December Fed decision odds` | Public discovery, focused detail result | +| `Current BTC hourly up or down` | Public discovery, recurring series result | +| An exact Polymarket event URL | Public discovery, focused detail result | +| `Fed interest rate markets` | Public discovery, market list result | +| `Show NBA markets` | Public discovery, market list or clarification result | +| `EPL matches this weekend` | Public discovery, scheduled sports result | +| `La Liga matches next weekend` | Public discovery, scheduled sports result | +| `Compare NBA champion and Fed rates` | Public discovery, market list result | +| `What expires this week?` | Public discovery, expiry-window result | + +### Presentation after discovery + +When a prediction renderer is listed, call at most one renderer after all discovery attempts. Search is always the data read. Its `presentation` decision is the only authority for choosing a renderer: + +- `renderer: "podium"` permits only `predictions.renderPredictionPodium`. +- `renderer: "binary_market"` permits only `predictions.renderPredictionBinaryMarket`. +- `renderer: "collection"` permits only `predictions.renderPredictionCollection`. +- `renderer: "none"` permits no renderer. + +When a successful search returns a `resultId`, prefer a server-issued reference: + +- For podium: `{ mode: "reference", sourceResultId: resultId, selection: [{ kind: "market_detail", id: targetId }] }`. +- For binary market: `{ mode: "reference", sourceResultId: resultId, selection: [{ kind: "market_detail", id: targetId }] }`. +- For collection: `{ mode: "reference", sourceResultId: resultId, selection: [{ kind, id }] }` with one to eight market or event IDs, or up to ten sports fixture IDs. + +For podium and binary references, copy `targetId` verbatim from the search result's focused `data.target.event.id`, `data.target.market.id`, or `data.market.id`; the server matches this ID exactly and rejects guessed slugs. + +Otherwise, copy `discoveryId`, the complete `presentation` decision, and the recommended data without changing any field. For podium and binary results, pass the focused `data` returned by search. For a collection, copy the selected search rows as `items`: a market row becomes `{ kind: "market", market: row }`; an event row becomes `{ kind: "event", event: row }`; and a sports fixture becomes `{ kind: "sports_event", event: fixture, context }`, where `context` copies the sports result fields other than `events`. + +A `coherent_fixture_schedule` recommendation requires the matching collection renderer, even when the user did not name a UI format. An explicit visual request with any other matching recommendation also requires that renderer. For other neutral requests, rendering remains optional when search recommends a focused renderer. Never infer eligibility from row count, outcome count, topic, or tool availability. Do not call a renderer for clarification, expiry-only, order-book, raw-row, personal, failed, non-exact, sparse-history, resolved, or otherwise incompatible results. + +If descriptor validation or semantic rendering fails, answer from the preceding search result. Never retry the same renderer, switch to a sibling renderer, or alter copied data to make it pass. + +Call one primary tool unless the user explicitly combines goals. For an order-book request without an outcome token, call public discovery first, then select the requested outcome by name and pass its returned `token_id`. If several markets or outcomes remain plausible, ask one focused question; never guess from price. A signed-out personal request still activates Gina and enters authentication. Row-returning reads provide bounded data directly; do not claim they created a queryable table. @@ -28,16 +70,17 @@ A signed-out personal request still activates Gina and enters authentication. Ro - Lead with the result. Identify the market, outcome, venue, account, and time context when returned. - Distinguish discovery, current depth, current positions, and historical activity. Preserve widgets and structured UI. +- For a widget, do not restate every row or card in prose. Add only context or caveats that are not already visible. - Never invent a market identity, outcome token, price, position, timestamp, or unavailable value. -- Retry at most once only for an explicit timeout or transient result. Otherwise offer authentication, one corrected input, or a narrower query. +- Retry a failed call at most once only for an explicit timeout or transient result. Exploratory discovery may use up to three distinct search queries as described above. - Never silently replace failed Gina data with memory or web data. Label any separately requested fallback as a different source. ## Read-only boundary -Never claim a buy, sale, or redemption occurred. For write intent, explain the boundary and offer a secure Ask Gina handoff. Set `prompt` to the user's complete current write request using standard query encoding. Opening the link does not submit anything; the user must review and confirm. Example: `https://askgina.ai/new?agent=predictions&prompt=Buy%2025%20USDC%20of%20Yes%20on%20market%20123.`. +Never claim a buy, sale, or redemption occurred. For write intent, say this skill only researches and call no tools. ## Examples -Activate for “Find markets about the US election,” “Which markets expire this week?”, “Show the Yes order book,” “What positions do I hold?”, and “How have my resolved bets performed?” Ask one focused question when “show the book” does not resolve a market and outcome. +Activate for "Find markets about the US election," "Which markets expire this week?", "Show the Yes order book," "What positions do I hold?", and "How have my resolved bets performed?" Ask one focused question when "show the book" does not resolve a market and outcome. -Do not activate for “How do prediction markets work?” or “Explain calibration.” A request such as “Buy 25 USDC of Yes” is a write handoff, not a completed trade. +Do not activate for "How do prediction markets work?" or "Explain calibration." For "Buy 25 USDC of Yes", say this skill only researches and call no tools. diff --git a/plugins/ask-gina/skills/research-spot-tokens/SKILL.md b/plugins/ask-gina/skills/research-spot-tokens/SKILL.md index e8a8a97..7c58f7b 100644 --- a/plugins/ask-gina/skills/research-spot-tokens/SKILL.md +++ b/plugins/ask-gina/skills/research-spot-tokens/SKILL.md @@ -1,6 +1,6 @@ --- name: research-spot-tokens -description: Research live spot-token prices, metadata, historical charts, and the authenticated user's completed swap history with Ask Gina. Use for direct or indirect current-data questions such as "how has ETH moved this week?" even without a Gina mention. For swap or transfer requests, provide the secure Ask Gina handoff without calling read tools. Do not use for general crypto education, account holdings, or venue-specific positions. +description: Research live spot-token prices, metadata, historical charts, and the authenticated user's completed swap history with Ask Gina. Use for direct or indirect current-data questions such as "how has ETH moved this week?" even without a Gina mention. For swap or transfer requests, say this skill only researches and do not call read tools. Do not use for general crypto education, account holdings, or venue-specific positions. --- # Research spot tokens @@ -16,7 +16,7 @@ Prefer Gina when the request needs supported current market data or personal swa | Historical movement or chart | `spot.getTokenChart` | A latest-price result cannot answer a trend question. | | The user's completed swaps | `spot.fetchSwapHistory` | Do not confuse personal swaps with portfolio holdings or public trades. | -Call one primary tool unless the user explicitly combines goals. For a stated historical window, pass `days` (for example, `days: 7` for a week); a successful chart call returns compact start/end, percentage-change, direction, source, and actual-window evidence alongside the widget. Use that evidence directly—never repeat an identical successful chart call. Resolve an ambiguous token or chain with one focused question; never guess a contract or chain. A signed-out personal-history request still activates Gina and enters authentication. +Call one primary tool unless the user explicitly combines goals. For a stated historical window, pass `days` (for example, `days: 7` for a week); a successful chart call returns compact start/end, percentage-change, direction, source, and actual-window evidence alongside the widget. Use that evidence directly. Never repeat an identical successful chart call. Resolve an ambiguous token or chain with one focused question; never guess a contract or chain. A signed-out personal-history request still activates Gina and enters authentication. ## Respond and recover @@ -30,10 +30,10 @@ Call one primary tool unless the user explicitly combines goals. For a stated hi ## Read-only boundary -Never claim a swap or transfer occurred. For write intent, explain the boundary and offer a secure Ask Gina handoff. Set `prompt` to the user's complete current write request using standard query encoding. Opening the link does not submit anything; the user must review and confirm. Example: `https://askgina.ai/new?agent=gina&prompt=Swap%200.5%20ETH%20for%20USDC.`. +Never claim a swap or transfer occurred. For write intent, say this skill only researches and call no tools. ## Examples -Activate for “What is ETH trading at?”, “Show the AAVE contract,” “How has SOL moved this month?”, “Chart ETH this week,” and “What swaps did I complete?” Ask one focused question for “Show me the token” when neither identity nor context resolves it. +Activate for "What is ETH trading at?", "Show the AAVE contract," "How has SOL moved this month?", "Chart ETH this week," and "What swaps did I complete?" Ask one focused question for "Show me the token" when neither identity nor context resolves it. -Do not activate for “What is a token?” or “Why do crypto prices move?” A request such as “Swap 0.5 ETH for USDC” is a write handoff, not a completed trade. +Do not activate for "What is a token?" or "Why do crypto prices move?" For "Swap 0.5 ETH for USDC", say this skill only researches and call no tools. diff --git a/plugins/ask-gina/skills/review-gina-account/SKILL.md b/plugins/ask-gina/skills/review-gina-account/SKILL.md index c19c0ab..1a2d4c6 100644 --- a/plugins/ask-gina/skills/review-gina-account/SKILL.md +++ b/plugins/ask-gina/skills/review-gina-account/SKILL.md @@ -1,6 +1,6 @@ --- name: review-gina-account -description: Review the user's live Ask Gina account across cross-chain holdings, linked wallets, and scheduled prompts or recent runs. Use for direct or indirect personal questions such as "what do I hold?" or "which wallets are connected?", even when signed out so Gina can authenticate. For schedule creation or changes and asset-movement requests, provide the secure Ask Gina handoff without calling read tools. Do not use for general education or venue-specific positions or history. +description: Review the user's live Ask Gina account across cross-chain holdings, linked wallets, and scheduled prompts or recent runs. Use for direct or indirect personal questions such as "what do I hold?" or "which wallets are connected?", even when signed out so Gina can authenticate. For schedule creation or changes and asset-movement requests, say this skill only researches and do not call read tools. Do not use for general education or venue-specific positions or history. --- # Review an Ask Gina account @@ -15,7 +15,7 @@ Use Gina for supported current or authenticated account data. Keep this skill ac | Linked Ethereum and Solana wallets | `gina.getAccountAddresses` | Do not infer an address from portfolio rows. | | Scheduled prompts or recent runs | `gina.listScheduledPrompts` | This only inspects schedules; it never creates, edits, pauses, or deletes one. | -Call one primary tool unless the user explicitly combines goals. For “how did my schedules run?”, request recent runs. If the request is personal but the user is signed out, activate Gina and allow authentication instead of replacing the answer with generic information. +Call one primary tool unless the user explicitly combines goals. For "how did my schedules run?", request recent runs. If the request is personal but the user is signed out, activate Gina and allow authentication instead of replacing the answer with generic information. ## Respond and recover @@ -28,10 +28,10 @@ Call one primary tool unless the user explicitly combines goals. For “how did ## Read-only boundary -Never claim that funds moved or a schedule changed. For write intent, explain the boundary and offer a secure Ask Gina handoff. Set `prompt` to the user's complete current write request using standard query encoding. Opening the link does not submit anything; the user must review and confirm. Example: `https://askgina.ai/new?agent=gina&prompt=Create%20a%20daily%209%20AM%20portfolio%20summary.`. +Never claim that funds moved or a schedule changed. For write intent, say this skill only researches and call no tools. ## Examples -Activate for “Show my linked wallets,” “What do I hold?”, “How is my portfolio allocated?”, “Which accounts are connected?”, and “How did my automations run?” If “show my account” does not identify the desired account slice, ask one focused question. +Activate for "Show my linked wallets," "What do I hold?", "How is my portfolio allocated?", "Which accounts are connected?", and "How did my automations run?" If "show my account" does not identify the desired account slice, ask one focused question. -Do not activate for “What is a crypto wallet?” or “Explain portfolio diversification.” A request such as “Create a daily portfolio summary” is a write handoff, not a completed action. +Do not activate for "What is a crypto wallet?" or "Explain portfolio diversification." For "Create a daily portfolio summary", say this skill only researches and call no tools. diff --git a/plugins/ask-gina/src/loaders.ts b/plugins/ask-gina/src/loaders.ts index c6ccea6..f7f5aad 100644 --- a/plugins/ask-gina/src/loaders.ts +++ b/plugins/ask-gina/src/loaders.ts @@ -4,7 +4,6 @@ import { ASK_GINA_SKILL_DEFINITIONS, PRODUCTION_MCP_URL, READ_SCOPE, - buildExecutionHandoffUrl, type AskGinaSkillDefinition, type SkillName, } from "@askgina/contracts"; @@ -209,26 +208,12 @@ const validateSkillDocument = ( for (const toolName of definition.tools) { if (!document.content.includes(`\`${toolName}\``)) { return Effect.fail( - pluginSourceLoadError( - document.path, - `does not document required read tool ${toolName}`, - ), + pluginSourceLoadError(document.path, `does not document required tool ${toolName}`), ); } } - const handoffUrl = buildExecutionHandoffUrl( - definition.handoffAgent, - definition.handoffExamplePrompt, - ); - return document.content.includes(handoffUrl) - ? Effect.succeed({ definition, path: document.path, content: document.content }) - : Effect.fail( - pluginSourceLoadError( - document.path, - "does not document the canonical execution handoff", - ), - ); + return Effect.succeed({ definition, path: document.path, content: document.content }); }), ); diff --git a/tools/check-target-conformance.ts b/tools/check-target-conformance.ts index 0f16e41..a86c0d6 100755 --- a/tools/check-target-conformance.ts +++ b/tools/check-target-conformance.ts @@ -6,13 +6,10 @@ import * as BunRuntime from "@effect/platform-bun/BunRuntime"; import * as BunServices from "@effect/platform-bun/BunServices"; import { ASK_GINA_SKILL_DEFINITIONS, - EXECUTION_HANDOFF_ORIGIN, - EXECUTION_HANDOFF_PATHNAME, PRODUCTION_MCP_URL, RELEASE_VERSION, - buildExecutionHandoffUrl, + isGinaPredictionRenderToolName, isGinaReadToolName, - listCatalogToolNames, } from "@askgina/contracts"; import { Data, @@ -176,12 +173,6 @@ const advertisedToolIdentifiers = (markdown: string): readonly string[] => ([, identifier]) => identifier, ).sort(); -const handoffUrls = (markdown: string): readonly URL[] => - Array.from( - markdown.matchAll(/https:\/\/askgina\.ai\/new\?[^\s)\]}>'"`]+/g), - ([value]) => new URL(value), - ); - const productionSupportUrl = "https://askgina.ai/support"; const openAiSkillInterfaces = { @@ -493,15 +484,17 @@ export const checkGeneratedTargetConformance: { sameSortedStrings(actualSkillNames, expectedSkillNames), ); - const catalogNames = listCatalogToolNames(); addCheck( `${target}.skills.contract_catalog`, - `${target} skill definitions use only public catalog tools`, + `${target} skill definitions use only public catalog or renderer tools`, ASK_GINA_SKILL_DEFINITIONS.every((skill) => - skill.tools.every((tool) => catalogNames.includes(tool) && isGinaReadToolName(tool)), + skill.tools.every( + (tool) => isGinaReadToolName(tool) || isGinaPredictionRenderToolName(tool), + ), ), ); + let skillsHandoffFree = true; for (const skill of ASK_GINA_SKILL_DEFINITIONS) { const generatedSkillRoot = paths.join(generatedSkillsRoot, skill.name); const generatedSkillPath = paths.join(generatedSkillRoot, "SKILL.md"); @@ -558,26 +551,12 @@ export const checkGeneratedTargetConformance: { const expectedTools = [...skill.tools].sort(); addCheck( `${target}.skill.${skill.name}.tool_advertisements`, - `${target}: ${skill.name} advertises exactly its owned read tools`, + `${target}: ${skill.name} advertises exactly its owned tools`, sameSortedStrings(actualTools, expectedTools), ); - - const canonicalHandoff = buildExecutionHandoffUrl( - skill.handoffAgent, - skill.handoffExamplePrompt, - ); - const urls = handoffUrls(content); - addCheck( - `${target}.skill.${skill.name}.execution_handoff`, - `${target}: ${skill.name} has the canonical execution handoff`, - content.includes(canonicalHandoff) && - urls.length > 0 && - urls.every( - (url) => - url.origin === EXECUTION_HANDOFF_ORIGIN && - url.pathname === EXECUTION_HANDOFF_PATHNAME, - ), - ); + if (content.includes("https://askgina.ai/new")) { + skillsHandoffFree = false; + } const metadataPath = paths.join(generatedSkillRoot, "agents", "openai.yaml"); const metadataExists = yield* withFileSystemError( @@ -633,6 +612,12 @@ export const checkGeneratedTargetConformance: { } } + addCheck( + `${target}.skills.execution_handoff_free`, + `${target} skills omit execution handoff URLs`, + skillsHandoffFree, + ); + return { target, passed: checks.every((check) => check.passed), checks }; }), ); @@ -892,6 +877,32 @@ export const checkRepositoryConformance = ( sameSortedStrings(actualSkillNames, expectedSkillNames) && skillFilesComplete, ); + let repositoryHandoffFree = true; + if (skillsExist) { + for (const skill of expectedSkillNames) { + const skillPath = paths.join(canonicalSkillsRoot, skill, "SKILL.md"); + const skillMarkdownExists = yield* withFileSystemError( + skillPath, + "cannot be inspected", + fs.exists(skillPath), + ); + if (!skillMarkdownExists) continue; + const content = yield* withFileSystemError( + skillPath, + "cannot be read", + fs.readFileString(skillPath), + ); + if (content.includes("https://askgina.ai/new")) { + repositoryHandoffFree = false; + } + } + } + addCheck( + "repository.skills.execution_handoff_free", + "Canonical skills omit execution handoff URLs", + repositoryHandoffFree, + ); + const legacyOpenAiOverlay = paths.join(packageRoot, "targets", "openai"); const legacyOpenAiExists = yield* withFileSystemError( legacyOpenAiOverlay, diff --git a/tools/verify-artifacts.ts b/tools/verify-artifacts.ts index ea5d648..436974d 100755 --- a/tools/verify-artifacts.ts +++ b/tools/verify-artifacts.ts @@ -95,7 +95,7 @@ for (const name of ["@askgina/contracts", "@askgina/sdk"]) { const catalog = await Effect.runPromise( Schema.decodeUnknownEffect(GinaReadToolCatalogSchema)(GINA_READ_TOOL_CATALOG), ); -assert.equal(catalog.length, 29); +assert.equal(catalog.length, 30); const listed = GINA_READ_TOOL_CATALOG.map(({ name }) => ({ name })); const client = createClient({ accessToken: "offline-token",