Skip to main content

Local Execution

Local Execution runs plugin actions synchronously in the current process. It requires no Ray cluster and provides immediate feedback, making it ideal for development and testing.

Overview

LocalExecutor is an in-process execution engine designed for fast feedback loops during development. Test and debug actions in your local environment without any cluster setup.

At a Glance

CharacteristicLocalTask (Ray Actor)Job (Ray Jobs API)Remote
LocationCurrent processRay cluster ActorRay Jobs APISynapse Backend
Startup TimeImmediate< 1s (worker reuse)Deferred (cluster submission)Network latency
Return ValueResult immediatelyResult immediatelyJob ID (async)Result via API
LoggingConsoleLogger (immediate)Ray worker logsLog streamingBackend logs
Use CaseDevelopment, debuggingFast repeated tasksHeavy/long-running workloadsProduction deployment
RequirementsNoneRay clusterRay clusterAuthentication

Good to know:

  • Local mode fully supports Python debuggers (pdb, breakpoint)
  • All output displays immediately in the console for rapid feedback

LocalExecutor

LocalExecutor executes actions synchronously in the current process.

Signature

class LocalExecutor:
def __init__(
self,
env: PluginEnvironment | dict[str, Any] | None = None,
job_id: str | None = None,
) -> None

def execute(
self,
action_cls: type[BaseAction],
params: dict[str, Any],
**kwargs: Any,
) -> Any

Parameters

__init__ Parameters

ParameterTypeRequiredDefaultDescription
envPluginEnvironment | dict | NoneNoNoneEnvironment config. Loads from os.environ if None. Passed as PluginEnvironment to RuntimeContext.
job_idstr | NoneNoNoneJob identifier for tracking

execute Parameters

ParameterTypeRequiredDescription
action_clstype[BaseAction]YesBaseAction subclass to execute
paramsdict[str, Any]YesParameter dictionary to pass to the action

Returns

Returns the result from the action's execute() method directly.

Errors/Exceptions

ExceptionCondition
ValidationErrorParameters fail params_model schema validation
ExecutionErrorException raised during action execution

Example

from synapse_sdk.plugins.executors.local import LocalExecutor
from my_plugin.actions import TrainAction

# Basic usage
executor = LocalExecutor()
result = executor.execute(TrainAction, {'epochs': 10})

# With custom environment and Job ID
executor = LocalExecutor(
env={'API_KEY': 'xxx', 'DEBUG': 'true'},
job_id='train-001'
)
result = executor.execute(TrainAction, {'epochs': 10, 'batch_size': 32})

Using run_plugin

The run_plugin function is the main entry point for plugin execution, handling both discovery and execution in one call.

Basic Usage

from synapse_sdk.plugins.runner import run_plugin

# Execute via module path
result = run_plugin('my_plugins.yolov8', 'train', {'epochs': 10})

# Execute via filesystem path
result = run_plugin('/path/to/plugin', 'train', {'epochs': 10})

# Execute via config.yaml path
result = run_plugin('/path/to/config.yaml', 'train', {'epochs': 10})

Signature

def run_plugin(
plugin_code: str,
action: str,
params: dict[str, Any] | None = None,
*,
mode: Literal['local', 'task', 'job'] = 'local',
**executor_kwargs: Any,
) -> Any

Parameters

ParameterTypeRequiredDefaultDescription
plugin_codestrYes-Module path ('my_plugins.yolov8') or filesystem path ('/path/to/plugin')
actionstrYes-Action name to execute (e.g., 'train', 'infer')
paramsdict | NoneNoNoneAction parameter dictionary
modeLiteral['local', 'task', 'job']No'local'Execution mode

executor_kwargs

OptionTypeDescription
action_clstype[BaseAction]Explicit action class (skips discovery)
envPluginEnvironment | dictEnvironment configuration
job_idstrJob identifier

With Custom Environment

from synapse_sdk.plugins.runner import run_plugin
from synapse_sdk.plugins.context import PluginEnvironment

# Pass environment as dictionary
result = run_plugin(
'my_plugins.yolov8',
'train',
{'epochs': 10},
env={'API_KEY': 'xxx', 'DEBUG': 'true'}
)

# Pass as PluginEnvironment object
env = PluginEnvironment({'API_KEY': 'xxx', 'BATCH_SIZE': '64'})
result = run_plugin(
'my_plugins.yolov8',
'train',
{'epochs': 10},
env=env
)

With Explicit Action Class

Skip plugin discovery by specifying the action class directly.

from synapse_sdk.plugins.runner import run_plugin
from my_plugin.train import TrainAction

# Explicit action class (skips discovery)
result = run_plugin(
'yolov8', # plugin_code is ignored
'train',
{'epochs': 10},
action_cls=TrainAction
)

CLI Usage

Use synapse plugin run to execute plugins from the command line.

Basic Command

# Basic local execution
synapse plugin run train

# With parameters
synapse plugin run train --params '{"epochs": 10, "batch_size": 32}'

# Explicit local mode
synapse plugin run train --mode local

Options

OptionTypeDefaultDescription
--plugin, -pstrAuto-detectPlugin code (auto-detects from config.yaml)
--pathPath.Plugin directory path
--paramsJSON{}Action parameters (JSON format)
--mode, -mlocal|task|job|remoteInteractiveExecution mode
--ray-addressstrautoRay cluster address (for task/job modes)
--gpusint-Number of GPUs to request
--cpusint-Number of CPUs to request
--debug/--no-debugbooltrueDebug mode

Remote Mode Options

OptionTypeDefaultDescription
--hoststr-Synapse API host (for remote mode)
--token, -tstr-Access token (for remote mode)

Inference Options

OptionTypeDefaultDescription
--input, -iJSON-JSON input for inference (for infer action)
--infer-pathstr/Inference endpoint path (for infer action)

Development Options

OptionTypeDefaultDescription
--debug-sdkboolfalseBundle local SDK with upload (for SDK development)

Examples

# Test plugin in another directory
synapse plugin run train --path /path/to/plugin

# Specify mode explicitly
synapse plugin run train --mode local --params '{"epochs": 5}'

# Run on Ray cluster with GPU
synapse plugin run train --mode task --gpus 1

# Run via Ray Jobs API
synapse plugin run train --mode job --params '{"epochs": 10}'

# Run via Synapse Backend API (requires authentication)
synapse plugin run deploy --mode remote

Environment Setup

PluginEnvironment

PluginEnvironment manages environment variables and configuration.

from synapse_sdk.plugins.context import PluginEnvironment

# Load from os.environ
env = PluginEnvironment.from_environ()

# Load with prefix filtering
env = PluginEnvironment.from_environ(prefix='MYAPP_')

# Load from TOML file
env = PluginEnvironment.from_file('config.toml')

# Create directly from dictionary
env = PluginEnvironment({'API_KEY': 'xxx', 'DEBUG': True})

Typed Accessors

Type-safe methods for accessing values.

from synapse_sdk.plugins.context import PluginEnvironment

env = PluginEnvironment.from_environ()

# Type-specific getters
api_key = env.get_str('API_KEY')
batch_size = env.get_int('BATCH_SIZE', default=32)
debug = env.get_bool('DEBUG', default=False)
learning_rate = env.get_float('LEARNING_RATE', default=0.001)
tags = env.get_list('TAGS', default=[]) # Supports comma-separated strings
MethodReturn TypeDescription
get(key, default)AnyReturns raw value
get_str(key, default)str | NoneConverts to string
get_int(key, default)int | NoneConverts to integer
get_float(key, default)float | NoneConverts to float
get_bool(key, default)bool | NoneConverts to boolean ('true', '1', 'yes' supported)
get_list(key, default)list | NoneConverts to list (comma-separated strings supported)

Environment Priority

Environment variables are applied in the following priority (later overrides earlier):

  1. os.environ (default)
  2. config.yaml env section
  3. CLI --env option or env parameter passed in code

config.yaml env Section

Define environment variables directly in your plugin's config.yaml.

code: my-plugin
name: My Plugin

env:
API_KEY: your-api-key
DEBUG: "true"
BATCH_SIZE: "32"
LEARNING_RATE: "0.001"

Merging Environments in Code

from synapse_sdk.plugins.context import PluginEnvironment

# Merge multiple environments (later takes precedence)
base_env = PluginEnvironment.from_environ()
config_env = PluginEnvironment.from_file('config.toml')
override_env = PluginEnvironment({'DEBUG': 'true'})

merged = PluginEnvironment.merge(base_env, config_env, override_env)

Debug Mode

Python Debugger Support

LocalExecutor runs in the current process, enabling full Python debugger support.

from synapse_sdk.plugins.action import BaseAction

class TrainAction(BaseAction[TrainParams]):
def execute(self):
# breakpoint() works
breakpoint()

# Or use pdb directly
import pdb; pdb.set_trace()

return {'status': 'done'}

Console Logging

LocalExecutor uses ConsoleLogger by default, displaying all logs immediately in the console.

class TrainAction(BaseAction[TrainParams]):
def execute(self):
# Progress display (immediate console output)
for epoch in range(self.params.epochs):
self.ctx.set_progress(epoch + 1, self.params.epochs)
self.ctx.log('epoch_complete', {'epoch': epoch, 'loss': 0.1})

# Record metrics (second argument is step name)
self.ctx.set_metrics({'final_loss': 0.05}, 'training')

return {'status': 'done'}

Good to know: The set_metrics() method requires a step name as the second argument to categorize metrics.

Limitations

Local Execution Constraints

LimitationDescriptionAlternative
Single processNo parallel/distributed processingUse mode='task' or mode='job'
No resource isolationUses host resources directlyUse Ray cluster resource management
No scalabilitySingle machine constraintUse Ray mode for production
No GPU allocationnum_gpus option not supportedRequest GPUs in Ray mode

Warning: Use local mode only for development and testing. Ray-based execution modes are recommended for production environments.

Local vs Ray Comparison

Best Practices

Development Workflow

  1. Test locally first: Always start with local mode when developing new actions
# Rapid iteration testing
synapse plugin run train --mode local --params '{"epochs": 1}'
  1. Use small datasets: Validate with small sample data for local testing
# Small dataset for development
result = run_plugin('my_plugin', 'train', {
'epochs': 1,
'batch_size': 4,
'sample_size': 100 # Small sample
})
  1. Leverage debug mode: Use breakpoints when issues occur
env = {'DEBUG': 'true'}
result = run_plugin('my_plugin', 'train', params, env=env)

Migration to Ray

Switch to Ray mode for scalability after local testing is complete.

from synapse_sdk.plugins.runner import run_plugin

# During development: local mode
result = run_plugin('my_plugin', 'train', params, mode='local')

# After testing: Ray Actor mode (fast startup)
result = run_plugin('my_plugin', 'train', params, mode='task')

# Production: Ray Job mode (heavy workloads)
job_id = run_plugin('my_plugin', 'train', params, mode='job')

Good to know: Action code remains identical regardless of execution mode. Just change the mode parameter.