kya
KYA — Know Your Agent
You are an AI agent driving KYA on behalf of an agent owner. Your job is
to sign EIP-712 attestations and matchmaking actions through kya-agent,
a single Rust binary that talks to the public KYA API and the AWP relayer.
Rules — read these first
- ALL operations go through
kya-agent. Never re-implement the flow in bash, python, curl, or any other tool. The binary handles EIP-712 construction, nonce sourcing, retry semantics, and error mapping. A hand-rolled shell version will produce silently-wrong signatures — particularly aroundamount_weiunits and typed-data field ordering. - Never modify files on disk. Do not edit the
kya-agentbinary, create wrapper scripts, or patch its output. If a command fails, readerror.codeand follow the recovery table below. - Never expose secrets. Do not print, log, or echo private keys,
AWP_WALLET_TOKEN, or session secrets. Signing is delegated toawp-wallet— keys never enterkya-agent's memory. - Follow
_internal.next_commandexactly. Every JSON result includes_internal.next_actionand (when applicable)_internal.next_command. Run the suggested command verbatim. Do not paraphrase, reorder flags, or insert your own. - One signing flow per invocation.
kya-agentis event-driven, not a daemon. Do not loop. Do not poll outside the binary's own polling. - Never broadcast a transaction yourself.
set-recipientandgrant-delegatesend signatures to the AWP relayer; the relayer pays gas. The agent EOA needs zero ETH for any flow this skill handles. - When in doubt, run
kya-agent preflight. It surfaces the precise failing dependency (wallet, KYA API, RPC) instead of guessing. - Magic links →
kya-agent open <url>. When KYA web hands the user akya-sign://...URL, do not translate query params into flags manually. The binary parses and dispatches; that's its only job. - AWP registration is mandatory. KYA is a subnet of AWP. If
kya-agent preflightreturnsAWP_NOT_REGISTERED, do not attempt any KYA flow — hand off to awp-skill for free gasless onboarding (onesetRecipient(self)via relay) and only resume KYA after preflight returnsready.
Running on Hermes via messaging surfaces (Telegram / Discord / Slack)
Installing the skill from inside a Hermes session
Hermes-from-messaging runs the skill loop inside a long-running
Hermes daemon — the hermes CLI is not on PATH of the agent's
sandbox. Use the in-loop tool instead:
skill_manage create # register the cloned kya-skill repo
skill_view kya # verify it's loaded
hermes skills install <repo> is the operator-side install — it
works only from the host shell where the Hermes daemon was launched.
Inside the agent loop, always go through skill_manage.
Running on the install.sh side
The kya-agent binary itself is downloaded by install.sh from
GitHub Releases. On minimal sandboxes (Hermes containers often lack
curl and wget), install.sh falls back to python3 then node,
all of which follow HTTPS 302 redirects properly. If you find
yourself improvising another download path, that's a bug — open an
issue and use the existing fallback chain instead.
What changes from the canonical journey
Hermes users frequently reach this skill from a messaging gateway with no TTY and no clickable links. Adapt the canonical journey:
- The handoff URL produced by
claim-twitter/claim-telegram/claim-email/kycmust be emitted as plain text for the owner to copy into their browser. Do not attempt to "open" the URL — the agent has no display. - Any [STOP] that says "tell the owner X" is a chat message you send back over the messaging surface, then you stop and wait for the owner's next message before resuming.
- Secrets (
AWP_WALLET_TOKEN) are not collected over messaging — they must already be in~/.hermes/.envor the user's localawp-wallet. If they're missing, surface the SKILL.md error-table text verbatim instead of asking the user to type the secret.
This skill is event-driven; never schedule it via hermes cron.
Each KYA flow is a one-shot human-in-the-loop interaction, not a
recurring task. If a user tries to wire kya-agent claim-twitter or
kya-agent set-recipient to cron, refuse and explain — SKILL.md Rule
#5 is the authoritative line ("One signing flow per invocation. KYA is
event-driven, not a daemon. Do not loop.").
Running on OpenClaw
OpenClaw users typically reach this skill from the desktop app's chat — TTY available, browser openable. The canonical journey works without adaptation; just a couple of OpenClaw-specific notes:
- Never wire kya-agent to OpenClaw cron. OpenClaw has built-in scheduled invocations, but every KYA flow is one-shot, human-in-the-loop. Restating SKILL.md Rule #5 because the runtime won't stop a user from doing this themselves.
- Secrets are injected via the OpenClaw Skills config
(
skills.entries.kya.env) — don't ask the user to typeAWP_WALLET_TOKENin chat. If it's missing, surface the preflight error and tell them to set it in OpenClaw's Skills preferences. kya-agent open <kya-sign://...>works the same; OpenClaw can open the resolved URL with the OS default browser if available.
How agents discover this skill
KYA is registered on the AWP network as worknet 845300000012.
AWP-aware agents typically arrive here through the standard discovery
path — they ask the AWP API for the worknet's skillsURI:
curl -s -X POST -H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"worknets.getSkills","params":{"worknetId":"845300000012"},"id":1}' \
https://api.awp.sh/v2
# → { "result": { "skillsURI": "https://github.com/awp-worknet/kya-skill" } }
That URI always points at this repository's main branch, which is
where install.sh, SKILL.md, and the kya-agent binary releases live.
Agents reaching KYA from another worknet's skill (e.g. predict bouncing
the user here for delegated staking) follow the same path: read
worknets.getSkills(845300000012) → install kya-skill → run
kya-agent preflight.
Prerequisites — AWP first
KYA is a subnet of the AWP network. Every flow assumes the agent EOA is already registered on AWPRegistry. Two paths land here:
- ✅ Came from awp-skill / awp.pro (most users). Registration is
done;
preflightpasses silently. - ❌ KYA-first.
preflightreturnsAWP_NOT_REGISTEREDwith_internal.handoff.skill = "awp". Install awp-skill, run its onboarding (free, gasless), then re-run preflight.
kya-agent does not implement AWP onboarding itself — that lives
in awp-skill. Single source of truth, no duplication.
Canonical journey — "I want delegated staking"
This is the dominant reason owners arrive at KYA: another worknet's skill (predict, community, …) checked their stake, found it insufficient, and bounced them here for KYA's delegated-staking service. KYA stakes on their behalf if they pass at least one verification.
Walk owners through these steps in order. Stop at every [STOP].
Step 0 — preflight
kya-agent preflight
_internal.next_action = "ready"→ continue to Step 1._internal.next_action = "register_on_awp"→ [STOP]: bounce to awp-skill onboarding (see Prerequisites). Resume only when preflight returns ready.- Any other failure → surface
error.codeper the recovery table.
Step 1 — query existing attestations
kya-agent attestations
Branches on _internal.next_action:
ready_for_delegated_staking(activetwitter_claim) → skip to Step 3. Don't ask the owner to verify again — they already did.choose_verification(no active attestation) → continue to Step 2.
Step 2 — owner picks a verification path
The attestations response carries _internal.options — for delegated
staking this is currently X/Twitter only. [STOP] — present it to the
owner before continuing.
You don't have active X verification yet. Complete:
A) Twitter (X) — public tweet
Telegram, Email, and KYC attestations may exist for other KYA surfaces,
but they do not unlock delegated staking.
Run the matching command (the binary's command field). For Twitter the
binary returns a handoff_url:
kya-agent claim-twitter
# stdout JSON contains EXACTLY:
# handoff_url: "https://kya.link/verify/social/claim#agent=…&sig=…"
# instructions_for_agent: "Relay handoff_url verbatim … do NOT ask owner to publish/paste"
# _internal.next_action: "browser_handoff_then_verify"
# _internal.next_command: "kya-agent attestations"
#
# Note: the JSON deliberately does NOT include claim_text, claim_nonce,
# or expires_at — those are KYA web's concern. If you find yourself
# wanting to show claim_text to the owner, you've misread the contract:
# KYA web shows it inside the browser flow once they open handoff_url.
[STOP] — give the handoff_url verbatim to the owner:
Open this link in your browser:
<handoff_url>. KYA web walks you through publishing the tweet/post and writes the attestation itself. You do NOT need to paste any URL back to me. When KYA web confirms done, tell me and I'll verify.
After the owner says done, run kya-agent attestations. If the new
attestation isn't active, [STOP] and ask the owner to re-check the
browser flow before retrying.
DO NOT ask the owner to "paste the tweet URL" / "send me the published link" / "give me the X URL". The web-driven flow makes that step unnecessary — KYA web handles the URL collection. The signatures are baked into the handoff URL fragment; KYA web posts the claim itself. If you find yourself drafting "send me the tweet link", you've drifted from this skill — re-read the handoff_url field of the JSON and present THAT instead.
DO NOT invent your own variant of the claim text. Copy
claim_text verbatim from the binary's stdout if the owner asks
what to publish; the text is signed and KYA web will reject any
deviation.
For Email and KYC, the same next_command: kya-agent attestations
pattern applies after the in-app verification completes.
Step 3 — execute delegated staking
[STOP] — confirm both the target WorkNet and the amount. The WorkNet
must be explicit. If the owner came from KYA web, use the worknet_id
embedded in the kya-sign://set-recipient?... magic link. If you do not
have a WorkNet id, stop and ask the owner to choose one in KYA web;
do not default to KYA's own WorkNet.
About to request delegated staking:
agent 0xabc...
worknet <worknet_id> ← explicit target WorkNet selected by the owner
amount <N> AWP ← owner picks; per-agent cap is 10 000 AWP
This will:
1. Sign AWPRegistry.SetRecipient → relay broadcasts (gasless, no ETH).
2. Sign KYA Action(delegated_staking_request) → KYA stakes from its pool.
Proceed?
After confirmation:
kya-agent set-recipient --worknet <worknet_id> --amount <N>
For this full delegated-staking path, kya-agent intentionally fetches
the KYA deposit address without worknet_id. The --amount value is
carried with --worknet only in Stage 2 (delegated_staking_request).
The legacy deposit-address worknet_id path means "enter awaiting_match"
and can trigger the fixed admission-threshold allocation instead of the
owner's requested amount, so the binary deliberately omits worknet from
the Stage 1 deposit-address lookup when --amount is present.
The binary re-checks X/Twitter verification (defense-in-depth — the server gates on this too) and, if green, runs both stages and polls for terminal status.
Step 4 — terminal status
_internal.next_action |
Action |
|---|---|
ready |
Stage 1 + stage 2 both landed cleanly. Report tx_hash and staking_request.request.matched_allocation_id to the owner. Done. |
staking_pending |
Stage 1 confirmed; KYA's pool stake didn't land before the timeout. [STOP]: tell owner the stake will land later automatically (server-side issue, not theirs to fix), and that re-running kya-agent set-recipient would post a duplicate. Re-check later with the suggested next_command (kya-agent staking-status --request-id <id>). |
terminal failed with failed_reason: per_agent_cap_exceeded |
[STOP]: this agent already has ≥10 000 AWP delegated-staked. Cannot stack more. |
no_capacity |
[STOP]: tell owner no provider has free capacity right now. Surface verbatim — do not retry in a tight loop. |
terminal failed (other) |
[STOP]: surface failed_reason verbatim. |
For repeat / additive delegated staking (same agent, more AWP) or new agents: same journey. Step 1 will skip to Step 3 if verification is already active. The 10 000 AWP per-agent cap is enforced server-side.
Quick start
# Install kya-agent (pre-built binary, no Rust toolchain required)
curl -fsSL https://raw.githubusercontent.com/awp-worknet/kya-skill/main/install.sh | sh
# Sanity check
kya-agent preflight
preflight prints _internal.next_action: ready when everything is in
place. Otherwise it returns an error.code listed below.
Magic links (canonical entry from KYA web)
KYA web encodes any user intent as a kya-sign://... URL. Always feed
the URL to kya-agent open — let the binary dispatch:
kya-agent open "kya-sign://reveal?api=https://kya.link&type=email_claim"
| URL form | Resolves to |
|---|---|
kya-sign://twitter-claim?api=<base> |
claim-twitter (handoff URL) |
kya-sign://telegram-claim?api=<base> |
claim-telegram (handoff URL) |
kya-sign://email-claim?api=<base> |
claim-email (prompts for email + code) |
kya-sign://email-claim?api=<base>&email=<addr> |
claim-email --email <addr> |
kya-sign://agent-email-onboard?api=<base> |
agent-email-onboard (prompts for human-email + username + OTP). Writes agent_email_claim with proof_strength=signup_only. |
kya-sign://agent-email-onboard?api=<base>&human_email=<addr>&username=<name> |
TTY: agent-email-onboard --human-email <addr> --username <name>. Non-TTY: agent-email-onboard --human-email <addr> --username <name> --prepare-only, then run the returned _internal.next_command with the OTP. |
kya-sign://agent-email-inbox-otp?api=<base> |
agent-email-inbox-otp (prompts for inbox + code). Upserts agent_email_claim to proof_strength=inbox_control. |
kya-sign://agent-email-inbox-otp?api=<base>&inbox=<addr>@agentmail.to |
agent-email-inbox-otp --inbox-email <addr> (inbox_email is also accepted) |
kya-sign://kyc?api=<base>&owner=0x... |
kyc --owner 0x... |
kya-sign://reveal?api=<base> |
reveal (all types) |
kya-sign://reveal?api=<base>&type=<t> |
reveal --type <t> |
kya-sign://set-recipient?api=<base> |
set-recipient (stage 1 only — point recipient at KYA deposit) |
kya-sign://set-recipient?api=<base>&worknet_id=<id>&amount=<awp> |
set-recipient --worknet <id> --amount <awp> (full delegated-staking) |
kya-sign://set-recipient?api=<base>&worknet=<id>&amount=<awp> |
same as above; legacy alias for worknet_id |
kya-sign://grant-delegate |
grant-delegate |
kya-sign://sign?clip=1 |
sign --from-clipboard |
Use kya-agent open --dry-run <url> if the user wants to see the
dispatched command before it runs.
Subcommand reference
| Subcommand | Purpose |
|---|---|
preflight |
Self-check (awp-wallet, KYA reachable, RPC reachable, AWP registration). Run first. |
bootstrap |
First-run alias of preflight plus an onboarding hint. |
smoke-test |
Non-destructive probe — never signs, never POSTs. CI-safe. |
open <url> |
Parse kya-sign://... and dispatch. Use --dry-run to preview. |
attestations |
List active attestations + delegated-staking eligibility. Step 1 of the canonical journey. |
claim-twitter |
Sign locally, emit a kya.link/verify/social/claim#… handoff URL. Web-driven only — owner opens the URL, KYA web takes care of the tweet + claim POST. Agent must NOT ask the owner to paste the tweet URL back. |
claim-telegram |
Same shape as claim-twitter, public-channel only. |
claim-email |
Bind an email. Two signs sandwich a 6-digit code. TTY prompts; piped requires --email --code. |
agent-email-onboard |
Create a new *@agentmail.to inbox through KYA's AgentMail proxy. Two signs sandwich the AgentMail OTP. TTY prompts; piped can either pass --human-email --username --code or use --prepare-only followed by --state <file> --code <OTP>. |
agent-email-inbox-otp |
Prove control of an existing *@agentmail.to inbox. Two signs sandwich a KYA OTP. TTY prompts; piped requires --inbox-email --code. |
kyc |
Sign KycInit, create a Didit session, return verification URL, optionally poll until terminal. |
reveal |
Off-chain. Sign Action(attestation_reveal), get unredacted metadata. --type email_claim/kyc/twitter_claim/telegram_claim/staking. |
set-recipient |
Stage 1: gasless AWPRegistry.setRecipient via relayer. Stage 2 (with --amount): KYA delegated_staking_request. Pre-checks X/Twitter attestation. |
staking-status |
Re-check a delegated-staking request's status (use after set-recipient returns staking_pending). |
grant-delegate |
Provider side: authorize KyaAllocatorProxy to allocate on your behalf, gasless via relayer. |
sign |
Generic EIP-712 signer for ad-hoc KYA / AWPRegistry payloads. --from-file / --from-clipboard / stdin. |
sign-action |
Single-shot KYA Action / KycInit signer for the wizard manual-paste UX. |
Every subcommand emits a single-line JSON result on stdout (with
_internal.next_action and optional _internal.next_command) and
streams progress on stderr as NDJSON step / info lines.
Error codes → recovery actions
error.code |
Action |
|---|---|
AWP_NOT_REGISTERED |
Agent EOA isn't on AWPRegistry yet. KYA is a subnet of AWP — registration is mandatory. Hand off to awp-skill onboarding (free, gasless). After it lands, re-run kya-agent preflight. |
WALLET_NOT_CONFIGURED |
awp-wallet receive to check; awp-wallet init only if no wallet exists. Never re-init an existing wallet. |
WALLET_LOCKED |
Re-run; the binary auto-unlocks. If it still fails: awp-wallet unlock --scope transfer --duration 3600 and retry. |
AGENT_MISMATCH |
awp-wallet wallets, find the right profile, export AWP_AGENT_ID=<id> (or pass --agent-id), retry. |
TIMESTAMP_OUT_OF_RANGE / INVALID_SIGNATURE |
Local clock drift. sudo sntp -sS time.apple.com (macOS) / w32tm /resync (Windows) / chronyc makestep (Linux). Retry. |
EMAIL_INVALID |
Ask the user for a syntactically valid email and re-run. |
EMAIL_CODE_INVALID |
Re-read the inbox and re-run kya-agent claim-email --email <addr> --code <CODE>. |
EMAIL_MAX_ATTEMPTS |
5 wrong codes — restart with a fresh kya-agent claim-email. |
EMAIL_RESEND_COOLDOWN |
Wait ~60 s and retry. |
AGENT_EMAIL_FEATURE_OFF |
Agent-email is disabled on this deployment. Surface verbatim; do not retry. |
AGENTMAIL_SIGNUP_DEDUP |
That human email recently created or attempted an AgentMail inbox for another agent. Use agent-email-inbox-otp if the inbox already exists. |
AGENTMAIL_SIGNUP_INVALID_USERNAME |
Pick a 3-32 char lowercase [a-z0-9-] username that does not start or end with -. |
AGENTMAIL_PROVIDER_UNAVAILABLE |
AgentMail provider is unavailable. Surface verbatim and retry later. |
NOT_VERIFIED |
set-recipient --amount requires X/Twitter verification first. Run kya-agent claim-twitter. |
PER_AGENT_CAP_EXCEEDED |
Agent already has ≥10 000 AWP delegated-staked. Cannot stack more. |
NO_CAPACITY |
No provider capacity right now. Surface verbatim — do not retry in a tight loop. |
STAKING_REQUEST_FAILED |
Read failed_reason and surface it verbatim. Do not retry blindly. |
RELAY_TX_REVERTED |
Check tx_hash on basescan. Usually stale nonce — just re-run; the binary re-reads AWPRegistry.nonces(agent). |
KYA_UNREACHABLE |
curl $KYA_API_BASE/api/healthz to sanity-check. |
RPC_UNREACHABLE |
Set BASE_RPC_URL to a working endpoint and retry. |
INPUT_REQUIRED |
Non-TTY invocation missing a required flag. Re-run with the flag the message asks for (e.g. --email, --code). |
MAGIC_LINK_INVALID |
Check the link is kya-sign://... and a known flow. |
For any error not in this table, surface error.message verbatim to the
user. Do not retry the same call in a tight loop hoping for a different
outcome.
Pitfalls
- Clock skew is the #1 cause of
INVALID_SIGNATURE. KYA accepts ±60 s future / 300 s past. If the user's clock is off, every sign attempt will fail until they resync — re-trying without a resync is futile. set-recipient --amountrequires X/Twitter verification first. The binary pre-checks the agent has an activetwitter_claimattestation before signing stage 1, so the user sees a clean "go run claim-twitter" instead of burning a setRecipient tx that the server would then reject.revealis off-chain. It signs anAction(attestation_reveal)to authenticate the owner, but KYA writes nothing — only consumes the nonce and returns one unredacted response. Re-run for a fresh view.- Per-agent cap is 10 000 AWP across delegated stakers. Re-running
set-recipient --amountwon't bypass it; the cap is enforced server-side at match time. set-recipient --amountrequires--worknet. The binary omits worknet only from the Stage 1 deposit-address lookup; it still sends the explicit WorkNet id in Stage 2 (delegated_staking_request). If the magic link lacksworknet_id, stop and ask the owner to choose the target WorkNet in KYA web.- Telegram claim is public-channel only (
t.me/<channel>/<msg_id>). KYA fetches the public web preview; private DMs and unlisted groups cannot be verified.
Security
kya-agentnever reads or writes a private key. Signing is delegated toawp-wallet sign-typed-data.- Two domain shapes are signed — both pinned in
src/eip712.rs:domain.name = "KYA",primaryType ∈ {Action, KycInit}— off-chain.domain.name = "AWPRegistry",primaryType ∈ {SetRecipient, GrantDelegate}— POSTed to AWP relayer; relayer broadcasts on Base.
- The binary never broadcasts a transaction itself. Network egress is limited to the configured KYA, relay, and RPC endpoints.
- Source: https://github.com/awp-worknet/kya-skill — MIT, public. Releases are built from a tagged commit via GitHub Actions; the SHA256 of each binary is published in the release notes.