Command Parsing

The OmniLinkEngine is a deterministic command parser that matches incoming text against predefined templates, extracts typed variables, and dispatches to registered handler functions. It bridges the gap between free-form AI output and structured local execution.


Overview

When an AI engine produces a command like open_the_front_door, the OmniLinkEngine matches it against your registered templates, extracts the variable values, and calls the appropriate handler. This gives you deterministic, type-safe command execution on the local side while letting the AI handle reasoning and decision-making in the cloud.

The engine runs entirely locally — no network calls, no API credits. It is designed to be fast, predictable, and easy to test.


Template Syntax

Templates use a simple format: literal text with typed variable placeholders enclosed in square brackets.

# Format
action_[varname:typename]

# Examples
open_the_[door:door]
set_temp_[temp:temperature]
move_[direction:direction]_[distance:int]_meters
send_message_to_[user:username]

Each placeholder follows the pattern [varname:typename] where:

  • varname — The name of the extracted variable. This is the key you use to access the value in your handler.
  • typename — The name of a registered type in the TypeRegistry. The engine uses the type’s regex pattern to match and capture the value.

Underscore and Space Normalization

The engine normalizes both underscores and spaces before matching. This means the following inputs all match the template open_the_[door:door]:

  • open_the_front_door
  • open the front_door
  • open the front door

This normalization makes the engine forgiving of formatting differences between AI outputs and human inputs.


TypeRegistry

The TypeRegistry maps type names to regex patterns. When the engine encounters a [varname:typename] placeholder, it looks up the type’s pattern and uses it to match the corresponding segment of the input string.

from omnilink import TypeRegistry

types = TypeRegistry()

# Register an enum-like type with specific allowed values
types.register("door", r"(?:front_door|back_door|garage_door)")

# Register a numeric type
types.register("temperature", r"\d+(?:\.\d+)?")

# Register a direction type
types.register("direction", r"(?:north|south|east|west|up|down)")

# Register a general string identifier
types.register("username", r"[a-zA-Z_][a-zA-Z0-9_]*")

Built-in Type Coercion

For common numeric types, the engine performs automatic coercion:

  • int — Captured values are converted to Python int.
  • float — Captured values are converted to Python float.

For all other types, captured values are returned as strings. You can perform additional parsing in your handler if needed.

Regex Tips

Type patterns are standard Python regular expressions. Keep these guidelines in mind:

  • Use non-capturing groups (?:...) for alternation to avoid interfering with the engine’s internal capture groups.
  • Be as specific as possible. A pattern like .* will match too greedily and may consume parts of subsequent template segments.
  • Test your patterns independently with Python’s re module before registering them.

Creating an Engine

Create an OmniLinkEngine instance by passing a list of template strings and an optional TypeRegistry:

from omnilink import OmniLinkEngine, TypeRegistry

types = TypeRegistry()
types.register("door", r"(?:front_door|back_door|garage_door)")
types.register("temperature", r"\d+(?:\.\d+)?")

engine = OmniLinkEngine(
    [
        "open_the_[door:door]",
        "close_the_[door:door]",
        "set_temp_[temp:temperature]",
        "get_status",
    ],
    types=types,
)

The engine compiles all templates into regex patterns at construction time. Subsequent handle() calls are fast pattern matches with no compilation overhead.


Handler Registration

Use engine.on_template() to bind a handler function to a specific template. When the engine matches an incoming command to that template, your handler is called with an event dictionary.

def handle_open_door(evt):
    door = evt["vars"]["door"]
    messenger = evt["messenger"]
    messenger.info(f"Opening {door}...")

    # Your actual door-opening logic here
    activate_motor(door)

    messenger.success(f"{door} is now open.")
    return {"status": "opened", "door": door}

engine.on_template("open_the_[door:door]", handle_open_door)

The Event Object

Every handler receives an evt dictionary with the following keys:

Key Type Description
vars dict A dictionary of extracted variable values, keyed by variable name. For example, evt["vars"]["door"] yields "front_door".
messenger AgentFeedback A feedback object for sending status messages back to the caller. Supports .info(), .success(), .warning(), and .error() methods.
template string The template string that matched, e.g. "open_the_[door:door]".

Testing Commands Locally

Call engine.handle() with a command string to test matching and handler execution without any cloud connectivity:

result = engine.handle("open the front_door")
print(result)
# {"matched": True, "result": {"status": "opened", "door": "front_door"}}

Result Format

The handle() method returns a dictionary with two keys:

Key Type Description
matched bool True if the command matched a registered template, False otherwise.
result any The return value from the matched handler. If no match was found, this is None.

No-Match Handling

When a command does not match any template, the engine returns:

result = engine.handle("fly_to_the_moon")
print(result)
# {"matched": False, "result": None}

You should always check the matched field in your integration code and handle unrecognized commands gracefully — for example, by logging a warning or asking the AI to rephrase.


Multiple Templates

An engine can hold any number of templates. The engine tests them in registration order and returns the first match:

engine = OmniLinkEngine(
    [
        "open_the_[door:door]",
        "close_the_[door:door]",
        "lock_the_[door:door]",
        "unlock_the_[door:door]",
        "set_temp_[temp:temperature]",
        "get_status",
        "emergency_stop",
    ],
    types=types,
)

# Register handlers for each template
engine.on_template("open_the_[door:door]", handle_open)
engine.on_template("close_the_[door:door]", handle_close)
engine.on_template("lock_the_[door:door]", handle_lock)
engine.on_template("unlock_the_[door:door]", handle_unlock)
engine.on_template("set_temp_[temp:temperature]", handle_temp)
engine.on_template("get_status", handle_status)
engine.on_template("emergency_stop", handle_emergency)

Use engine.list_templates() to retrieve all registered templates at any time:

templates = engine.list_templates()
print(templates)
# ["open_the_[door:door]", "close_the_[door:door]", "lock_the_[door:door]", ...]

Advanced Patterns

Multi-Variable Templates

Templates can contain multiple variable placeholders. The engine extracts all of them into the vars dictionary:

types.register("axis", r"(?:x|y|z)")
types.register("distance", r"-?\d+(?:\.\d+)?")

engine = OmniLinkEngine(
    ["move_[axis:axis]_[dist:distance]_mm"],
    types=types,
)

def handle_move(evt):
    axis = evt["vars"]["axis"]
    dist = float(evt["vars"]["dist"])
    return {"axis": axis, "distance_mm": dist}

engine.on_template("move_[axis:axis]_[dist:distance]_mm", handle_move)

result = engine.handle("move_x_15.5_mm")
# {"matched": True, "result": {"axis": "x", "distance_mm": 15.5}}

Templates Without Variables

Not every command needs variables. Simple action commands work as plain strings:

engine = OmniLinkEngine(["get_status", "emergency_stop", "reset"])

def handle_status(evt):
    return {"temperature": 22.5, "doors": "all_locked"}

engine.on_template("get_status", handle_status)

Complex Regex Types

For more sophisticated matching, register complex regex patterns:

# Match ISO 8601 dates
types.register("date", r"\d{4}-\d{2}-\d{2}")

# Match email-like patterns
types.register("email", r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}")

# Match hex color codes
types.register("color", r"#[0-9a-fA-F]{6}")

# Match file paths
types.register("filepath", r"[a-zA-Z0-9_/.-]+")

Complete Working Example

Here is a full example that creates a smart-home command engine, registers types and handlers, and processes a sequence of commands:

from omnilink import OmniLinkEngine, TypeRegistry

# 1. Define types
types = TypeRegistry()
types.register("door", r"(?:front_door|back_door|garage_door)")
types.register("device", r"(?:living_room_light|kitchen_light|bedroom_fan|tv)")
types.register("temperature", r"\d+(?:\.\d+)?")
types.register("brightness", r"\d{1,3}")

# 2. Create engine with templates
engine = OmniLinkEngine(
    [
        "open_the_[door:door]",
        "close_the_[door:door]",
        "turn_on_[device:device]",
        "turn_off_[device:device]",
        "set_temp_[temp:temperature]",
        "set_brightness_[level:brightness]",
        "get_status",
    ],
    types=types,
)

# 3. Define handlers
home_state = {
    "doors": {"front_door": "closed", "back_door": "closed", "garage_door": "closed"},
    "devices": {"living_room_light": "off", "kitchen_light": "off", "bedroom_fan": "off", "tv": "off"},
    "temperature": 22.0,
    "brightness": 80,
}

def handle_open(evt):
    door = evt["vars"]["door"]
    home_state["doors"][door] = "open"
    evt["messenger"].success(f"{door} opened.")
    return {"door": door, "state": "open"}

def handle_close(evt):
    door = evt["vars"]["door"]
    home_state["doors"][door] = "closed"
    evt["messenger"].success(f"{door} closed.")
    return {"door": door, "state": "closed"}

def handle_on(evt):
    device = evt["vars"]["device"]
    home_state["devices"][device] = "on"
    return {"device": device, "state": "on"}

def handle_off(evt):
    device = evt["vars"]["device"]
    home_state["devices"][device] = "off"
    return {"device": device, "state": "off"}

def handle_temp(evt):
    temp = float(evt["vars"]["temp"])
    home_state["temperature"] = temp
    return {"temperature": temp}

def handle_brightness(evt):
    level = int(evt["vars"]["level"])
    home_state["brightness"] = min(100, max(0, level))
    return {"brightness": home_state["brightness"]}

def handle_status(evt):
    return home_state

# 4. Register handlers
engine.on_template("open_the_[door:door]", handle_open)
engine.on_template("close_the_[door:door]", handle_close)
engine.on_template("turn_on_[device:device]", handle_on)
engine.on_template("turn_off_[device:device]", handle_off)
engine.on_template("set_temp_[temp:temperature]", handle_temp)
engine.on_template("set_brightness_[level:brightness]", handle_brightness)
engine.on_template("get_status", handle_status)

# 5. Process commands
commands = [
    "open the front_door",
    "turn on living_room_light",
    "set_temp_23.5",
    "set_brightness_60",
    "get_status",
]

for cmd in commands:
    result = engine.handle(cmd)
    if result["matched"]:
        print(f"OK: {cmd} -> {result['result']}")
    else:
        print(f"NO MATCH: {cmd}")

Integration with AI Chat

The command engine is most powerful when combined with the AI chat API. The AI engine produces structured commands, and the local engine parses and executes them:

from omnilink.client import OmniLinkClient

client = OmniLinkClient(omni_key="omk_your_key")

# The AI response will contain a command from the agent's availableCommands
response = client.chat(
    "It's getting cold, can you warm it up?",
    agent_name="home-controller",
    engine="g1-engine",
)

# Parse the AI's command output
ai_command = response["command"]  # e.g., "set_temp_24"
result = engine.handle(ai_command)

if result["matched"]:
    print(f"Executed: {result['result']}")
else:
    print(f"Unrecognized command: {ai_command}")

Next Steps