Chapter 21Lesson 01180–240 min

Dynamic and Hybrid Libraries, Remote Libraries, and Extension Architecture: Core Concepts and Mental Model

Understand when runtime discovery, proxy dispatch, or process/language isolation justifies dynamic, hybrid, or Remote libraries—and what metadata, serialization, lifecycle, and trust contracts each boundary adds.

Robot Framework 7.4.2Static / hybrid / dynamicRemote libraryXML-RPC contractTrust boundary

Learning objectives

  • Explain static, hybrid, dynamic, and Remote library boundaries without treating them as interchangeable implementation styles.
  • Describe the dynamic discovery contract: names, arguments, types, tags, documentation/source metadata, and runtime dispatch.
  • Explain why hybrid discovery often preserves more static-library clarity than full dynamic dispatch.
  • Trace a Remote call across XML-RPC serialization, server execution, result dictionaries, and Robot result/log evidence.
  • Identify new ownership and security boundaries created by process or network isolation.

Current compatibility baseline — verified 2026-08-31. Robot Framework 7.4.2 is the stable course baseline and requires Python 3.8+. The mandatory examples use Robot Framework's current static, dynamic, hybrid, Libdoc, and built-in Remote interfaces. Dynamic libraries require get_keyword_names and run_keyword; metadata getters are optional but strongly recommended when the proxy knows signatures, types, tags, documentation, or source. Starting with Robot Framework 7.0, dynamic special methods may also be asynchronous, but this chapter keeps the learning path synchronous. Hybrid libraries perform dynamic name discovery but Robot calls Python methods directly. Remote is a dynamic proxy over XML-RPC and does not itself add authentication or transport security. The mandatory Remote exercise therefore binds a disposable protocol fixture only to 127.0.0.1, uses a bounded client timeout, and does not expose a remote-stop operation. The separately maintained robotremoteserver package is optional: its latest published 1.1.1 release explicitly documents Python support through 3.11, so it is not required for this course's Python 3.8+ local path.

1. The problem: extension flexibility can erase contracts if it is used casually

Chapter 20 established the preferred baseline: a small static Python library with explicit keywords, type information, a deliberate scope, and domain logic that remains testable without Robot Framework. That baseline is easy to inspect because Python methods, signatures, and documentation are present at import time.

Some integrations cannot use that simple model. A proxy may discover commands from a device at runtime. A tool may need to expose plugins that were not known when the Python package was written. A library implementation may need to run in another process, container, machine, or language runtime. These are valid reasons to add dynamic discovery or a Remote boundary—but they move part of the API contract from Python reflection into runtime metadata and transport behavior.

Default decision. If a finite keyword set can be expressed as normal Python methods, keep the static API from Chapter 20. Dynamic, hybrid, and Remote designs are escalation mechanisms, not “more advanced therefore better” replacements.

2. Inspect versions and extension boundaries before mutation

python --version
python -m robot --version
python -m robot.libdoc --help

# Verify that the built-in Remote library belongs to the same installation.
python -c "from robot.libraries.Remote import Remote; import inspect; print(inspect.getfile(Remote))"

# Optional: inspect the current public library API documentation source location.
python -c "import robot; print(robot.__version__); print(robot.__file__)"

These commands are read-only. They prove which Robot interpreter, Libdoc tool, and Remote implementation will be used. In CI, record them with the command line and selected Python executable; a dynamic or remote failure is impossible to reason about if the client and server versions are unknown.

3. Mental model: discovery and execution transport are separate contracts

Extension architecture flow
flowchart TD
A[Robot keyword lookup] --> B{Library API}
B -->|static| C[Python reflection]
B -->|hybrid| D[get_keyword_names + direct callable]
B -->|dynamic| E[get_keyword_names + metadata + run_keyword]
E --> F[Local runtime dispatch]
E --> G[Remote dynamic proxy]
G --> H[XML-RPC over loopback/private network]
H --> I[Remote server implementation]
I --> J[status / output / return / error]
J --> K[Robot output.xml + log/report]
C --> K
D --> K
F --> K

The first branch answers how Robot knows what keyword exists and what arguments it accepts. The second answers where the implementation runs. A dynamic library can still be entirely in-process. Conversely, the built-in Remote library is itself a dynamic proxy whose dispatch crosses an XML-RPC channel.

The final arrow back to Robot is also a contract. Local Python keywords normally return objects or raise exceptions. Remote servers must instead return a protocol dictionary with a mandatory status and optional output, return, error, traceback, continuable, and fatal entries.

4. Name each state store before changing it

Layer What it owns What it does not own
Robot suite/test/task Execution selection, status, Robot variables, result model Remote server memory or network listener
Static/hybrid Python object Instance state according to library scope Robot variable scope
Dynamic dispatcher Runtime keyword registry and dispatch mapping Durable external workflow state unless explicitly implemented
Remote client URI, timeout, discovered remote metadata cache Server authentication policy or server process lifetime
Remote server process Listening socket, implementation objects, process memory Robot test lifecycle unless a protocol call coordinates it
XML-RPC payload Serialized args/results Arbitrary Python object identity
Evidence artifacts output.xml/log/report, server log, Libdoc, ADR The actual external-system state

5. Dynamic API: runtime metadata becomes executable interface

from __future__ import annotations

from robot.api import logger

from .domain import normalize_token


class DynamicCatalog:
    """Runtime-discovered provider used only to demonstrate the dynamic API."""

    def get_keyword_names(self):
        return ["Normalize Token", "Describe Provider"]

    def get_keyword_arguments(self, name):
        specs = {
            "Normalize Token": ["token", "prefix=ID"],
            "Describe Provider": [],
        }
        return specs[name]

    def get_keyword_types(self, name):
        types = {
            "Normalize Token": {"token": "str", "prefix": "str"},
            "Describe Provider": {},
        }
        return types[name]

    def get_keyword_tags(self, name):
        return ["rf21", "dynamic"]

    def get_keyword_documentation(self, name):
        docs = {
            "__intro__": "Synthetic dynamic provider for Chapter 21.",
            "__init__": "No import arguments.",
            "Normalize Token": "Normalize a synthetic token and return prefix:value.",
            "Describe Provider": "Return the provider architecture name.",
        }
        return docs.get(name, "")

    def run_keyword(self, name, args, kwargs):
        logger.info(f"dynamic dispatch keyword={name}")
        if name == "Normalize Token":
            values = {"token": None, "prefix": "ID"}
            if len(args) > 2:
                raise AssertionError("Normalize Token accepts at most two positional arguments")
            for key, value in zip(("token", "prefix"), args):
                values[key] = value
            values.update(kwargs)
            return normalize_token(values["token"], values["prefix"])
        if name == "Describe Provider":
            return "dynamic"
        raise AssertionError(f"Unknown dynamic keyword: {name}")

get_keyword_names and run_keyword are mandatory for a true dynamic library. The optional getters are not decorative documentation: get_keyword_arguments lets Robot reject invalid calls before dispatch; get_keyword_types enables conversion; tags/documentation/source improve Libdoc, editor assistance, and diagnostics.

If the metadata says a keyword accepts one argument but run_keyword would actually accept two, the metadata wins at the Robot boundary. That is why dynamic metadata must be tested like any other public API.

6. Hybrid API: dynamic discovery, normal callables

from .domain import normalize_token


class HybridCatalog:
    """Dynamic discovery with normal Python callables for execution."""

    def get_keyword_names(self):
        # Important: direct method names must be returned in the same form.
        return ["normalize_token", "describe_provider"]

    def normalize_token(self, token: str, prefix: str = "ID") -> str:
        """Normalize a synthetic token."""
        return normalize_token(token, prefix)

    def describe_provider(self) -> str:
        """Return the provider architecture name."""
        return "hybrid"

Hybrid libraries implement get_keyword_names but do not implement run_keyword. Robot resolves the returned names to Python callables and then uses the same reflection-based signature/documentation behavior as the static API. The current User Guide therefore recommends hybrid over fully dynamic in many cases where runtime name discovery is required but proxy-style execution is not.

Subtle rule. Hybrid names that refer to methods must be returned in the same form as the Python method name. Returning My Keyword for a method named my_keyword is not equivalent in the hybrid lookup step.

7. Remote library: dynamic metadata plus a transport boundary

Robot Framework's built-in Remote library implements the client side of the Remote protocol. It connects to an XML-RPC server, discovers keywords, converts supported argument values, asks the server to run a keyword, then maps the server result dictionary back to Robot status/log/return behavior.

*** Settings ***
Library    Remote    http://127.0.0.1:8270    5s    AS    LocalRemote

*** Test Cases ***
Example
    ${value}=    LocalRemote.Normalize Token    ab 12    REF
    Should Be Equal    ${value}    REF:AB-12

The URI and timeout belong to the client import. The server process must already be listening before Robot imports the library. A timeout shorter than a remote keyword's legitimate execution time can interrupt the call; a huge timeout can make CI failures painfully slow. Treat it as a bounded transport setting, not a retry mechanism.

8. Serialization changes values—even when the keyword name looks local

Python-side value Remote protocol behavior Design implication
String / number / Boolean Passed without structural change Good for stable contracts
None Converted to an empty string by the documented Remote conversion rules Do not rely on Python None identity across the boundary
Tuple / iterable Transferred as a list recursively Do not promise tuple identity
Mapping Transferred as a dict; keys become strings Prefer explicit string keys
Returned dict Presented by Robot as dot-accessible mapping Useful, but still serialized data
Unsupported custom object Converted to a string by the documented generic conversion Define a DTO-like mapping/list/string contract instead

Process isolation therefore changes more than performance. It changes type fidelity, failure transport, logging paths, and the debugging sequence.

9. Remote is a capability endpoint, not merely a library import

A Remote server can execute whatever operations its hosted library exposes. The Remote protocol is XML-RPC over HTTP/HTTPS; it does not magically add application authentication, authorization, network segmentation, or secret management. DO NOT USE 0.0.0.0 for this lab. Binding a powerful automation server to all interfaces can turn a test helper into a network control plane.

  • Mandatory labs bind only to 127.0.0.1.
  • Use a private network and an authenticated/authorized transport boundary for any real remote deployment.
  • Expose the minimum keyword capability set.
  • Do not make remote stop, shell execution, credential retrieval, or production mutation reachable merely because they are convenient.
  • Preserve server-side and Robot-side evidence with correlation identifiers when production remote execution is justified.

10. The escalation ladder

  1. Static: known keyword surface, in-process—choose first.
  2. Hybrid: runtime discovery is needed, but direct callables remain practical.
  3. Dynamic: runtime registry/proxy dispatch genuinely needs a centralized run_keyword.
  4. Remote: process/machine/language isolation justifies serialization, networking, service lifecycle, and security overhead.

The architecture should stop at the first level that satisfies the requirement.

Knowledge check

Why is a Remote library not simply a dynamic library with a URL?

Which methods are mandatory for a true dynamic library?

Why is hybrid often preferable to dynamic when only names are discovered at runtime?

A remote server returns a custom Python object. What contract problem should you expect?

Next lesson

Dynamic and Hybrid Libraries, Remote Libraries, and Extension Architecture: Guided Hands-On Workflow

Continue with Dynamic and Hybrid Libraries, Remote Libraries, and Extension Architecture: Guided Hands-On Workflow. It builds directly on the state, evidence, and operating assumptions established here, so carry those constraints forward rather than treating the next page as an isolated topic.

References and version anchors

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.