Skip to content

Random notes about service discovery

Chip Morningstar edited this page Apr 16, 2026 · 1 revision

Idealized model

Simplified outline

  • Who are the actors?
    • Service provider
    • Service matcher
    • Service consumer
    • Relay -- necessary component due to libp2p but not part of the model per se

Detailed outline

  • Service matcher -- runs on VPS (or anywhere that LLM can run)

    • registration interface (running in a vat)
      • talking to service providers via ocap messaging
      • talking to matching engine using events dispatched by a kernel service
    • matching engine (LLM agent managed by harness running in Node?)
      • talking to registration interface via event hook handler
      • talking to query clients via external NL channel
  • Service provider -- runs on my laptop

    • MetaMask wallet ocap service API (running in a vat)
      • talking via kernel service interface to MetaMask wallet (running in a browser plugin)
        • Q: is there a non-browser version of this (e.g., Node)? I don't think we need the browser here:
          • running as a browser plugin is motivated by:
            • sophisticated UX framework
              • -> not used here because our UI is CLI/LLM
            • convenient delivery platform, leveraging the fact that everybody has a browser
              • -> don't care, this is for a specialized audience
      • talking via ocap messages to service matcher
        • registers its address + description(s) of the services it's providing
    • Other mock services simulating a service ecosystem, e.g.:
      • materials vendor
      • parts vendor
      • shipping service
      • custom 3d printing service
      • custom machining service
      • custom PC board maker
      • custom electronics assembly service
      • custom integrated manufacturing service
      • working capital finance
  • Service consumer -- runs on VPS

    • human user
      • talking to LLM agent via CLI
        • using service matching tool (can query for services based on what human says they want)
          • talking to service matcher via NL query interface
        • using service agent tool (knows what service methods can do from NL descriptions)
          • talking to service provider via ocap messages (knows how to invoke service methods from API spec)
  • Relay -- runs on VPS?

    • for the various entities that are vat hosted, there needs to be a libp2p relay so they can communicate

Status quo - deconstruction of Erik's demo

Terse breakdown

  • Who are the actors?
    • Service matcher -- doesn't exist
      • Doesn't exist - service consumer born with hard coded knowledge of provider and just asks for ocap
      • Provider responds with the demo ocap regardless of what it was asked for
    • Service provider -- runs on my laptop
      • MetaMask running in web browser as plugin
      • Ocap kernel running inside plugin realizes service API
      • Note: regular MetaMask plugin fork with ocap kernel, NOT our test plugin nor omnium-gatherum plugin
    • Service consumer -- runs on VPS
      • OpenClaw LLM running on VPS
      • Ocap kernel running in Node
        • LLM talks to ocap kernel via tool that issues queueKernelMessage calls
        • LLM has tool that issues queueKernelMessage (?)
        • (service consumer) ocap kernel talks to (service provider) ocap kernel via remote message interface
    • Relay -- runs on VPS?

Super detailed breakdown

  • Who are the actors?
    • Metamask browser plugin
      • Installed into browser on "home" laptop
      • Creates a group of specialized exos and registers them as kernel service objects:
        • hostApiProxy
          • Has a single method, invoke(method: string, args: unknown[]): unknown
            • invokes an operation provided by the browser plugin itself, described by methodCatalog
            • operations used directly are:
              • OcapKernelController:setCapabilityVendorURL
                • tells the plugin the ocap URL for the VPF
              • AccountsController:listAccounts
              • NetworkController:findNetworkClientIdByChainId
              • SignatureController:newUnsignedPersonalMessage
        • methodCatalog (DOES NOT APPEAR TO BE USED)
          • Maintains a catalog of methods invocable via hostApiProxy.invoke
          • Initialized from a static JSON
            • file metamask-extension/app/offscreen/ocap-kernel/services/method-catalog-data.json
            • encodes an array of MethodEntry objects, each having:
              • name -- string of the form ${controller}:${method}, used for invocation
              • signature -- TypeScript method signature string
              • description -- NL plaintext description
          • Provides methods:
            • getAllMethods(): MethodEntry[]
              • returns all the entries in the catalog
            • getControllers(): string[]
              • returns the controller strings
            • getMethodsByController(controller: string): string[]
              • returns the methods with a given controller
            • search(query: string): MethodEntry[]
              • returns all entries whose name or description matches query in the stupidest way
        • llmService
          • Simulates an LLM that you can prompt, provides a single method:
            • prompt(request: string): LlmResponse
              • Returns a data object:
                • capabilityName: string,
                • sourceCode: string,
                • description: string,
                • methodNames: string[]
              • Simulated response is a single static object with
                • capabilityName: 'PersonalMessageSigner',
                • sourceCode: JavaScript source for an exo with getAccounts and signMessage methods
                • description: a doc string for the same,
                • methodNames: ['getAccounts', 'signMessage']
            • The source code given in the sourceCode property seems to be real, and simulates the LLM being
              • asked for information about a service to sign messages
      • Creates ocap "home" kernel
        • Connects automatically to relay indicated by env var containing relay libp2p multiaddr
        • Loads a single vat cluster containing the capability-vendor vat
          • code in metamask-extension/app/offscreen/ocap-kernel/vats/capability-vendor/index.ts
          • creates an internal object "VPF" typed as a vendorPublicFacet with method:
            • requestCapability(request: string): CapabilityRecord
              • passes the request string to the llmService.prompt method and then passes the result of that to an invocation of ApprovalController:addRequest via hostApiProxy.invoke to get the user's approval (which will include an echo of the service description properties from the llm)
              • assuming it gets the approval, passes the sourceCode value to evaluate (in a compartment) and captures the result of that (an exo)
              • uses the exo and the other llm provided info (via the approval result) to populate a CapabilityRecord
              • returns "the" CapabilityRecord
          • stashes VPF descriptive record in internal capabilities map key by id (in this case 0)
          • creates root object typed as a vendorAdmin with methods:
            • bootstrap(<the usual>)
              • sets things up in the usual way
              • returns the VPF ocap url
            • getPublicFacet(): vendorPublicFacet
              • returns a reference to the VPF
            • other (vestigial?) methods that are not relevant here
      • Takes the bootstrap result (which is the VPF ocap URL) and gives it to the plugin via hostApiProxy object's invoke method, invoking OcapKernelController:setCapabilityVendorURL
    • Relay
      • A regular libp2p relay, running on the VPS
    • Ocap daemon
      • Node process loads ocap "away" kernel
        • No vats -- the kernel acts directly on requests for kernel-level services delivered via rpc
          • The kernel exposes key services via the rpc interface:
            • redeem-url
              • redeems ocap URL resulting in a kernel object (koXX) in the "away" kernel
                • this will generally involve talking via the relay to other kernels, notably the "home" kernel
            • queueMessage
              • enqueues an arbitary captp message to a kernel object onto the "way" kernel's run queue
                • the kernel will deliver this to either a local vat or a remote kernel
                  • in the current demo, this will be an object in the "home" kernel reachable via the relay
            • exec
              • executes an arbitrary operation on an arbitrary kernel rpc entry point
                • the kernel exposes all kinds of functionality this way
                • this operation is extremely hazardous and should only be used for debugging purposes
    • Demo...
      • in Manual mode: the human enters CLI commands of the form yarn ocap daemon ...
        • the ocap CLI app:
          • synthesizes rpc messages based on command line params
          • transmits them to the daemon process via the rpc socket it exposes
            • the daemon process parses these, feeds them to the kernel, serializes the result, and returns it
          • awaits the result and then prints it out
      • in LLM mode: the human types NL requests at the OpenClaw LLM harness
        • an OpenClaw plugin gives the LLM a tool it can use to talk to the daemon process as the CLI app would
      • in either mode (described here in terms of CLI operations, but LLM does the same thing)
          1. extract ocap url from MetaMask plugin UI -->
          1. yarn ocap daemon redeem-url -->
          1. yarn ocap daemon queueMessage requestCapability '["view user accounts"]'
            • approve request at MetaMask plugin prompt
            • --> JSON info, including and method names 'getAccounts' and 'signMessage' and copy of source code from llm info above
          1. yarn ocap daemon queueMessage ko5 getAccounts --> JSON info, including
          1. yarn ocap daemon queueMessage ko5 signMessage '["", "YOUR MESSAGE", "0x1"]'
            • confirm signing at MetaMask plugin prompt
            • --> ""

Flow + key objects created

  • MetaMask plugin offscreen.ts:init calls offscreen/ocap-kernel/index.ts:runKernel
  • runKernel creates:
    • (a) hostApiProxy kernel service exo
    • (b) llmService kernel service exo
    • (c) kernel with single vat subcluster containing the 'vendor' vat (which is the bootstrap vat)
      • vat defined by :buildRootObject
  • buildRootObject creates:
    • (d) 'vendorPublicFacet' exo (stored) "VPF"
    • (e) 'vendorAdmin' exo (returned as vat root) "VA"
  • VA.bootstrap returns ocap URL for VPF, which runKernel gives to plugin for revelation to user
  • user obtains VPF ocap URL from plugin UI
  • user agent redeems VPF ocap URL and receives VPF reference
  • user agent sends requestCapability message to VPF
  • VPF.requestCapability calls llmService.prompt
  • llmService.prompt returns static LlmResponse object "LLMR"
  • VPF.requestCapability calls hostApiProxy.invoke::ApprovalController:addRequest with LLMR contents
  • ApprovalController:addRequest prompts for user approval, returns CapabilityApprovalResult with LLMR contents
  • VPF.requestCapability creates Compartment endowed with hostApiProxy
    • (f) 'PersonalMessageSigner' by evaluating LLMR.sourceCode in Compartment ("PMS")
  • VPF.requestCapability returns CapabilityRecord containing ref to PMS
  • user agent sends getAccounts message to PMS
  • PMS.getAccounts calls hostApiProxy.invoke::AccountsController:listAccounts and returns result from that
  • AccountsController:listAccounts returns record including chainAddress
  • PMS.getAccounts returns this record to user
  • user agent sends signMessage with chainAddress, messageToSign, and chainId to PMS
  • PMS.signMessage calls hostApiProxy.invoke::NetworkController:findNetworkClientByChainId with chainId to get networkClientId
  • PMS.signMessage calls hostApiProxy.invoke::SignatureController:newUnsignedPersonalMessage with chainAddress, messageToSign, and networkClientId to get signedMessage
  • PMS.signMessage returns signedMessage to user

Key object sources

  • (All in metamask-extension/app/offscreen/ocap-kernel
  • (a) hostApiProxy kernel service: services/host-api-proxy.ts
  • (b) llmService kernel service: services/llm-service.ts
  • (d) capability vendor vat root VA: vats/capability-vendor/index.ts
    • (return value from buildRootObject)
  • (e) capability vendor public facet VPF: ocap-kernel/vats/capability-vendor/index.ts
    • (return value from bootstrap)
  • (f) 'PersonalMessageSigner' "PMS": services/llm-service.ts
    • (JS source inside literal string property of static result object returned from prompt)

The status quo vs. the idealized model

  • The status quo vs. the idealized model
    • In the demo, there is basically one service, signMessage
    • Service provider
      • The service provider is the PMS object
        • calling the hostApiProxy kernel service
          • calling the plugin's NetworkController and SignatureController objects to actually do the work
        • executing inside the 'vendor' vat
          • executing inside the "home" kernel
            • executing inside the MetaMask browser plugin
        • PMS definition is funky:
          • specified by the llmService query response as if it were describing something there but actually it's created by evaluating a string in the query response struct
    • Service matcher
      • There is no component corresponding to the service matcher as such
      • The VPF conflates the roles of service matcher and service contact point
        • The service consumer sends requestCapability to the VPF with a NL query string
          • This provides the service matcher function, sort of
            • In the fantasy the demo is pretending to be, this query string is interpreted by an LLM
            • In reality, it gives a canned response that is the same regardless of the query
              • This response is a description of the signMesage service
          • This provides the contact point function, sort of
            • MetaMask browser user must approve the vending of the service capability
              • The user is given information about the service itself, not the use
                • Not totally crazy: what they're really authorizing is PMS's use of the hostApiProxy
    • Service consumer
      • LLM mode demo closely resembles the intended end state, except:
        • Has to interact with the demo service provider & sort of matcher functionalities as they are
        • Doesn't actually benefit from having an LLM on the matching side
        • Doesn't try to reason about what API it is seeking based on vague mission statement
          • LLM reasoning is mostly about flexibility in how to consume a well understood functionality