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.
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
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
- Static: known keyword surface, in-process—choose first.
- Hybrid: runtime discovery is needed, but direct callables remain practical.
-
Dynamic: runtime registry/proxy dispatch
genuinely needs a centralized
run_keyword. - 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?
Because it adds a network/process transport and serialization contract, server lifecycle, timeout behavior, and a new trust boundary in addition to dynamic discovery.
Which methods are mandatory for a true dynamic library?
get_keyword_names and run_keyword. Metadata getters are optional, but omitting them weakens validation, conversion, documentation, and tooling.
Why is hybrid often preferable to dynamic when only names are discovered at runtime?
Robot can still call normal Python methods and obtain signatures/documentation by reflection, avoiding a custom dispatch and metadata implementation for everything.
A remote server returns a custom Python object. What contract problem should you expect?
The Remote protocol cannot preserve arbitrary Python object identity; unsupported types are converted, commonly to strings. Return a deliberate serializable data contract instead.
References and version anchors
- Robot Framework 7.4.2 — Creating test libraries — dynamic and hybrid API baseline
- Robot Framework 7.4.2 — Remote library interface — protocol, serialization, and execution contract
- Robot Framework 7.4.2 Remote library — client URI and timeout behavior
- Python Remote Server project — optional reference server; not required by the lab
- Robot Framework current releases — stable/pre-release anchor
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.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.