Context
The parent-control web app's pairing screen (apps/parent-control/app/_components/pairing.tsx) is a UI mockup, not wired to the real flow. A master can pair a real agent (e.g. the §10.2 Hermes-sandbox agent) only via the CLI harness (harness/phase1-wire-demo.sh Phase P); the web app cannot. This is the deferred "connect the agent — web-app side" follow-up from #207.
Goal: a master pairs a real agent from the parent-control web app — claim its one-time pairing code, register its device on-chain, grant its initial scope (Touch ID) — mirroring phase1-wire-demo.sh --real --webauthn, but driven from the web UI.
Current state
Works today (reusable):
- CLI pairing — the genuine §10.2 flow (
phase1-wire-demo.sh Phase P: depair → P.0 agent shows a code → P.1 master claims → P.2 registerAgentDevice → P.3 setScopeWithWebauthn).
- Agent side —
agentkeys-daemon --request-pairing generates a K10 device key in the sandbox + displays the one-time pairing code.
- Broker —
/v1/agent/pairing/claim (the CLI already hits it).
- Daemon — WebAuthn / Touch ID (K11 enroll) +
/v1/actors/:id/scope/grant (for existing actors).
Mock / missing:
pairing.tsx + App.tsx acceptPairing / refreshPairing operate on local React state (pairingRequests = useState([])); "accept · Touch ID" opens a ceremony modal with nothing real behind it.
- The daemon ui-bridge has no
/v1/agent/pairing/* routes — no claim, no poll-for-codes, no register-a-new-agent-device. Its routes cover email auth, K11 enroll, onboarding, existing-actor scope/revoke, memory/config/classify/credentials.
- The client seam (
apps/parent-control/lib/client/{types,daemon,empty}.ts) has no pairing methods.
Scope (the build)
- Daemon ui-bridge routes for the master-side web pairing, mirroring CLI Phase P:
- poll / accept a pairing code → broker
/v1/agent/pairing/claim (P.1)
- list awaiting-approval / retrieve pending (P.1c)
registerAgentDevice on-chain for the fresh device key (P.2)
- initial scope grant via the existing WebAuthn path (P.3) — reuse
setScopeWithWebauthn / the /v1/actors/:id/scope/grant machinery
- Client methods (
types.ts + daemon.ts + empty.ts stubs): listPairingRequests, acceptPairing / claimPairing.
- Wire
App.tsx: refreshPairing → real poll; acceptPairing → real claim → register → scope ceremony (replace the local-state mock).
- Docs: split
docs/operator-runbook-wire.md into Part 1 (CLI, existing) + Part 2 (web-app), and write the web-app pairing runbook.
Acceptance
- From the parent-control web app, a master claims a real Hermes-sandbox agent's pairing code,
registerAgentDevice lands on-chain, and the agent's memory:<ns> scope is granted via one Touch ID — with no CLI.
- The agent then reads its permitted memory — the same guarantee
phase1-wire-demo.sh --real --webauthn proves, but web-driven.
- The pairing UI's
requests come from a real daemon poll, not demo data; decline + the device list reflect real state.
- A web-app pairing runbook exists;
operator-runbook-wire.md is split CLI vs web.
Effort
~L (full-stack: daemon pairing routes + on-chain register glue + frontend client + wiring + runbook). The agent side, the broker claim endpoint, and WebAuthn already exist — so the bulk is the master-side web glue + on-chain register.
References
Flow clarifications (2026-06)
From the end-to-end onboarding flow review — these refine #214's scope (the master side):
- Authorization happens at pairing. When the master claims the agent's code and grants scope (P.3), it authorizes the agent's
cred:<service> + memory:<ns> scopes — including the LLM key the master previously vaulted (cred:openrouter, catalog category ai-services) and the memory namespaces the agent may read (e.g. kids / family). These are scope grants, not "config" access — DataClass::Config is master-only; the agent holds no config cap.
- Master designates a DEFAULT LLM key at authorization. The no-UI AI-toy case has no developer selection UI on the device, so the master-set default is what the agent uses out of the box. (A developer CLI can override —
--select 1, default = first.)
- Pairing display = runtime QR. When the device has a screen, the daemon shows the
pairing_code as a QR (arch.md §10.2 "DISPLAY pairing_code (QR / screen text)"). No-screen devices → send the code to a companion app — deferred (most cases covered; not a static manufacture-time package QR, so no pre-provisioning work).
Split with #216 (agent side): #214 covers the master side — claim → register → grant scope → designate the default LLM key. The agent side — pairing → cred-fetch the authorized vaulted LLM key → wire hermes plants it into Hermes + integrates the authorized memory (replacing the operator-env shortcut) — is tracked in #216 (blocked by #214). Together they are the full end-to-end "the agent uses the key + memory the master authorized."
Context
The parent-control web app's pairing screen (
apps/parent-control/app/_components/pairing.tsx) is a UI mockup, not wired to the real flow. A master can pair a real agent (e.g. the §10.2 Hermes-sandbox agent) only via the CLI harness (harness/phase1-wire-demo.shPhase P); the web app cannot. This is the deferred "connect the agent — web-app side" follow-up from #207.Goal: a master pairs a real agent from the parent-control web app — claim its one-time pairing code, register its device on-chain, grant its initial scope (Touch ID) — mirroring
phase1-wire-demo.sh --real --webauthn, but driven from the web UI.Current state
Works today (reusable):
phase1-wire-demo.shPhase P:depair→P.0agent shows a code →P.1master claims →P.2registerAgentDevice→P.3setScopeWithWebauthn).agentkeys-daemon --request-pairinggenerates a K10 device key in the sandbox + displays the one-time pairing code./v1/agent/pairing/claim(the CLI already hits it)./v1/actors/:id/scope/grant(for existing actors).Mock / missing:
pairing.tsx+App.tsxacceptPairing/refreshPairingoperate on local React state (pairingRequests = useState([])); "accept · Touch ID" opens a ceremony modal with nothing real behind it./v1/agent/pairing/*routes — no claim, no poll-for-codes, no register-a-new-agent-device. Its routes cover email auth, K11 enroll, onboarding, existing-actor scope/revoke, memory/config/classify/credentials.apps/parent-control/lib/client/{types,daemon,empty}.ts) has no pairing methods.Scope (the build)
/v1/agent/pairing/claim(P.1)registerAgentDeviceon-chain for the fresh device key (P.2)setScopeWithWebauthn/ the/v1/actors/:id/scope/grantmachinerytypes.ts+daemon.ts+empty.tsstubs):listPairingRequests,acceptPairing/claimPairing.App.tsx:refreshPairing→ real poll;acceptPairing→ real claim → register → scope ceremony (replace the local-state mock).docs/operator-runbook-wire.mdinto Part 1 (CLI, existing) + Part 2 (web-app), and write the web-app pairing runbook.Acceptance
registerAgentDevicelands on-chain, and the agent'smemory:<ns>scope is granted via one Touch ID — with no CLI.phase1-wire-demo.sh --real --webauthnproves, but web-driven.requestscome from a real daemon poll, not demo data; decline + the device list reflect real state.operator-runbook-wire.mdis split CLI vs web.Effort
~L (full-stack: daemon pairing routes + on-chain register glue + frontend client + wiring + runbook). The agent side, the broker claim endpoint, and WebAuthn already exist — so the bulk is the master-side web glue + on-chain register.
References
harness/phase1-wire-demo.shPhase P;docs/operator-runbook-wire.md.docs/arch.md§10.2 (agent-initiated pairing, method A).Flow clarifications (2026-06)
From the end-to-end onboarding flow review — these refine #214's scope (the master side):
cred:<service>+memory:<ns>scopes — including the LLM key the master previously vaulted (cred:openrouter, catalog categoryai-services) and the memory namespaces the agent may read (e.g. kids / family). These are scope grants, not "config" access —DataClass::Configis master-only; the agent holds no config cap.--select 1, default = first.)pairing_codeas a QR (arch.md §10.2 "DISPLAY pairing_code (QR / screen text)"). No-screen devices → send the code to a companion app — deferred (most cases covered; not a static manufacture-time package QR, so no pre-provisioning work).Split with #216 (agent side): #214 covers the master side — claim → register → grant scope → designate the default LLM key. The agent side — pairing → cred-fetch the authorized vaulted LLM key →
wire hermesplants it into Hermes + integrates the authorized memory (replacing the operator-env shortcut) — is tracked in #216 (blocked by #214). Together they are the full end-to-end "the agent uses the key + memory the master authorized."