skills/genie-npu-ir20/SKILL.md
# Skill: Genie NPU Node (ir20) – OpenAI-Compatible-ish API This skill teaches GoatCitadel how to use a specific **Windows ARM64 laptop** node (Tailscale name **`ir20`**) running **GenieAPIService** (FastAPI/Uvicorn) that exposes an **OpenAI-style HTTP API**. Use this node when you want: - **Local / private inference** (stays on your devices + mesh) - **NPU-backed** inference (when available) - A **mesh compute target** that other GoatCitadel nodes can call --- ## Node identity - **Node name
npx skillsauth add spurnout/goatcitadel skills/genie-npu-ir20Install this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
This skill teaches GoatCitadel how to use a specific Windows ARM64 laptop node (Tailscale name ir20) running GenieAPIService (FastAPI/Uvicorn) that exposes an OpenAI-style HTTP API.
Use this node when you want:
ir20GenieAPIService8910/v1GET /v1/models):
IBM-GraniteIBM-Granite-v3.1-8BPick the first that works.
Tailscale IP (preferred for mesh):
http://100.64.0.4:8910/v1
Tailscale MagicDNS hostname (if enabled):
http://ir20:8910/v1
LAN IP (same Wi‑Fi/LAN only):
http://192.168.0.108:8910/v1
0.0.0.0:8910 on the laptop (so LAN + Tailscale can reach it).100.64.0.0/10.GET /v1/modelsPOST /v1/chat/completionsPOST /v1/completions/v1/chat/completions.Swagger/OpenAPI may list:
POST /v1/images/generationsPOST /images/generationsThese are meant for image generation (like “DALL·E style” APIs). On this node, they may be present in OpenAPI but not actually wired to a working image model. Treat them as probe-only: try it, and if it errors, route image generation to another provider/node.
GET {base}/models
Expected:
POST {base}/chat/completions with a tiny request (max_tokens small).
Most requests look like this:
{
"model": "IBM-Granite",
"stream": false,
"messages": [
{ "role": "user", "content": "Reply with exactly: pong" }
],
"temperature": 0,
"max_tokens": 3
}
model (string): one of the IDs from /v1/modelsmessages (array): {role, content} chat messages
system, user, assistantstream (bool): true for SSE streaming, false for one-shottemperature (number): randomnessmax_tokens (int): keep small for control teststop_p (number): nucleus sampling (if supported)top_k (int): top‑k sampling (if supported)temp vs temperatureSome Genie/OpenAPI screens show temp while many OpenAI clients use temperature.
You already verified temperature works via Invoke-RestMethod. If a client requires temp, use it as a fallback:
{ "temp": 0.2 }
If both are present, prefer temperature unless Genie errors.
When stream: true, responses arrive like:
data: { ... "object":"chat.completion.chunk", ... }
data: { ... }
data: [DONE]
Each chunk contains partial text under something like:
choices[0].delta.contentImplementation tip:
delta.content fields until you see [DONE].: interpolation trapIn PowerShell, this can break:
$base = "http://$ip:8910/v1" # ❌ sometimes parses weird
Use either:
$base = "http://$($ip):8910/v1"
# or
$base = "http://{0}:8910/v1" -f $ip
A here-string by itself only prints. You must pipe it to a file:
@'
hello
'@ | Set-Content -Path .\skill.md -Encoding utf8
âï¸)chcp 65001
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
$base = "http://100.64.0.4:8910/v1"
curl.exe -s "$base/models"
$base = "http://100.64.0.4:8910/v1"
$body = @{
model="IBM-Granite"
stream=$false
messages=@(@{ role="user"; content="Reply with exactly: pong" })
temperature=0
max_tokens=3
} | ConvertTo-Json -Depth 10
$resp = Invoke-RestMethod -Method Post -Uri ($base + "/chat/completions") `
-ContentType "application/json" -Body $body
$resp.choices[0].message.content
$base = "http://100.64.0.4:8910/v1"
$payloadPath = "$env:TEMP\genie_stream.json"
@{
model="IBM-Granite"
stream=$true
messages=@(@{ role="user"; content="Count 1 to 10, each on a new line." })
temperature=0.2
} | ConvertTo-Json -Depth 10 | Set-Content -Encoding utf8 -NoNewline $payloadPath
curl.exe -N -s -X POST ($base + "/chat/completions") `
-H "Content-Type: application/json" `
--data-binary "@$payloadPath"
import requests
BASE = "http://100.64.0.4:8910/v1"
payload = {
"model": "IBM-Granite",
"stream": False,
"messages": [{"role": "user", "content": "Reply with exactly: pong"}],
"temperature": 0,
"max_tokens": 3,
}
r = requests.post(f"{BASE}/chat/completions", json=payload, timeout=30)
r.raise_for_status()
print(r.json()["choices"][0]["message"]["content"])
If GoatCitadel uses the OpenAI SDK pattern, set:
base_url to Genieapi_key to any dummy string (if Genie doesn’t enforce auth)from openai import OpenAI
client = OpenAI(
base_url="http://100.64.0.4:8910/v1",
api_key="local-no-auth",
)
resp = client.chat.completions.create(
model="IBM-Granite",
messages=[{"role": "user", "content": "Reply with exactly: pong"}],
temperature=0,
max_tokens=3,
)
print(resp.choices[0].message.content)
If the SDK complains about unsupported fields, fall back to the plain HTTP method.
When GoatCitadel needs text generation, it should:
http://100.64.0.4:8910/v1http://ir20:8910/v1http://192.168.0.108:8910/v1GET /models (cache for a short time, e.g., 60 seconds)IBM-Granite unless explicitly asked for IBM-Granite-v3.1-8BPOST /chat/completions with:
stream: false for short tool calls / structured answersstream: true for long-form interactive outputtemperature: 0.0 to 0.3max_tokens: small for “tool-ish” calls, larger for writing tasks100.64.0.0/10Meaning: something else already owns 8910.
Find it:
Get-NetTCPConnection -LocalPort 8910 -State Listen | Select-Object -First 1 | Format-List
Kill the owning PID (careful):
$pid = (Get-NetTCPConnection -LocalPort 8910 -State Listen | Select-Object -First 1).OwningProcess
Stop-Process -Id $pid -Force
Invoke-RestMethod returns weird truncated objectsPowerShell sometimes formats nested objects oddly. For inspection:
$resp | ConvertTo-Json -Depth 50
✅ Confirmed working:
/v1/models/v1/chat/completions (non-stream)/v1/chat/completions (stream via SSE)⚠️ Present but not guaranteed:
This skill currently hardcodes ir20 details. Later you can generalize by:
/v1/models to detect capabilitytesting
# Skill: Bankr Optional Integration (User-Managed) Use this skill only if you explicitly need Bankr workflows. This skill is intentionally optional and should stay disabled or sleep-mode until reviewed. ## Safety defaults 1. Default to read-only behavior unless the user clearly asks for a write action. 2. Require explicit approval before any money-moving action. 3. Enforce strict per-action and daily spend caps. 4. Reject unsupported chains, symbols, or action types. 5. Log every attempted a
data-ai
Setup your own "[email protected]" email here without your human -Autonomous email for AI agents.
development
Deep web research with citations and source validation.
testing
Validate behavior, catch regressions, and report defects with clear reproduction steps.