CLI for Laminar AI rollout debugging
npm install lmnr-cliLanguage-agnostic CLI for Laminar AI rollout debugging.
The Laminar CLI is a pure orchestrator that coordinates rollout debugging sessions between your code and the Laminar backend. It is designed to be completely language-agnostic, delegating language-specific execution to worker processes.
``bashRun directly with npx
npx lmnr-cli@latest dev agent.ts
Usage
`bash
lmnr-cli dev [file] [options]
OR
lmnr-cli dev -m [options]
`$3
-
-m, --python-module - Python module path (e.g., src.myfile)
- --function - Specific function to serve (if multiple rollout functions found)
- --project-api-key - Project API key (or set LMNR_PROJECT_API_KEY env variable)
- --base-url - Base URL for the Laminar API (default: https://api.lmnr.ai)
- --port - Port for the Laminar API (default: 443)
- --grpc-port - Port for the Laminar gRPC backend (default: 8443)
- --frontend-port - Port for the Laminar frontend (default: 5667)
- --external-packages - [ADVANCED] Packages to pass as external to esbuild
- --dynamic-imports-to-skip - [ADVANCED] Dynamic imports to skip
- --command - [ADVANCED] Custom worker command (e.g., python3, node)
- --command-args - [ADVANCED] Arguments for the custom command$3
`bash
TypeScript
npx lmnr-cli@latest dev agent.tsJavaScript (extracts parameter names at runtime)
npx lmnr-cli@latest dev agent.jsPython - Script mode
npx lmnr-cli@latest dev agent.pyPython - Module mode
npx lmnr-cli@latest dev -m src.agent
`Architecture
The CLI orchestrates three components:
1. SSE Client: Connects to Laminar backend and receives run events
2. Cache Server: Local HTTP server (port 35667) that provides cached spans to workers
3. Worker Process: Language-specific subprocess that executes your code
`
┌─────────────────┐
│ Laminar API │
└────────┬────────┘
│ SSE
│
┌────────▼────────┐ ┌──────────────┐
│ CLI Process │◄────►│ Cache Server │
│ (Orchestrator) │ │ (HTTP:35667)│
└────────┬────────┘ └──────▲───────┘
│ │
│ spawn │ HTTP
│ │
┌────────▼────────┐ ┌──────┴───────┐
│ Worker Process │─────►│ Your Code │
│ (TS/Python/etc) │ │ (agent.ts) │
└─────────────────┘ └──────────────┘
`Worker Protocol
The CLI communicates with workers using a simple JSON protocol over stdin/stdout.
$3
The CLI sends a single JSON line to the worker's stdin containing the configuration:
`typescript
{
filePath: string; // Path to the file to execute
functionName?: string; // Specific function to run
args: Record | any[]; // Arguments for the function
env: Record; // Environment variables
cacheServerPort: number; // Port of the cache server
baseUrl: string; // Laminar API base URL
projectApiKey?: string; // Project API key
httpPort: number; // HTTP port for Laminar API
grpcPort: number; // gRPC port for Laminar API
externalPackages?: string[]; // External packages (TS-specific)
dynamicImportsToSkip?: string[]; // Dynamic imports to skip (TS-specific)
}
`$3
Workers send messages prefixed with
__LMNR_WORKER__: to stdout:#### Log Message
`typescript
{
type: 'log';
level: 'info' | 'debug' | 'error' | 'warn';
message: string;
}
`#### Result Message
`typescript
{
type: 'result';
data: any; // Function return value
}
`#### Error Message
`typescript
{
type: 'error';
error: string; // Error message
stack?: string; // Stack trace
}
`$3
Any stdout without the
__LMNR_WORKER__: prefix is considered user output (e.g., console.log()) and passed through transparently.$3
The CLI sets these environment variables for the worker:
-
LMNR_ROLLOUT_SESSION_ID: Session ID for this rollout run
- LMNR_ROLLOUT_STATE_SERVER_ADDRESS: Cache server URL (http://localhost:35667)$3
Workers can query the cache server for cached spans:
Endpoint:
POST /cachedRequest:
`json
{
"path": "string", // Span path
"index": number // Span index
}
`Response:
`json
{
"span": {
"name": "string",
"input": "string", // JSON string
"output": "string", // JSON string
"attributes": {} // Parsed attributes
},
"pathToCount": {
"path1": 5,
"path2": 3
},
"overrides": {
"spanName": {
"system": "string or array",
"tools": [{
"name": "string",
"description": "string",
"parameters": {}
}]
}
}
}
`Implementing a Worker
To support a new language, implement a worker that:
1. Reads JSON configuration from stdin
2. Sets environment variables from
config.env
3. Loads and executes the specified function
4. Initializes your SDK's tracing (reads LMNR_ROLLOUT_STATE_SERVER_ADDRESS)
5. Sends protocol messages to stdout (prefixed with __LMNR_WORKER__:)
6. Exits with code 0 on success, non-zero on failure$3
The TypeScript worker is included in
@lmnr-ai/lmnr:`typescript
// Read config from stdin
const config = JSON.parse(await readStdin());// Set environment variables
for (const [key, value] of Object.entries(config.env)) {
process.env[key] = value;
}
// Initialize SDK
Laminar.initialize({
baseUrl: config.baseUrl,
projectApiKey: config.projectApiKey,
// ...
});
// Load and execute function
const module = require(config.filePath);
const result = await moduleconfig.functionName;
// Send result
sendMessage({ type: 'result', data: result });
// Exit successfully
process.exit(0);
`$3
`python
python -m lmnr.cli.worker
import json
import sys
import os
Read config from stdin
config = json.loads(sys.stdin.readline())Set environment variables
for key, value in config['env'].items():
os.environ[key] = valueInitialize SDK
import lmnr
lmnr.initialize(
base_url=config['baseUrl'],
project_api_key=config['projectApiKey'],
)Load and execute function
module = __import__(config['filePath'])
result = getattr(module, config['functionName'])(*config['args'])Send result
send_message({'type': 'result', 'data': result})Exit successfully
sys.exit(0)
`Default Worker Resolution
The CLI automatically resolves workers based on file extension:
| Extension | Worker Command |
|-----------|----------------|
|
.ts, .tsx | node @lmnr-ai/lmnr/dist/cli/worker.cjs |
| .js, .mjs, .cjs | node @lmnr-ai/lmnr/dist/cli/worker.cjs |
| .py | python3 -m lmnr.cli.worker |Custom workers can be specified with
--command and --command-args.Python Language Support
$3
The CLI needs to discover function metadata (name and parameters) to provide a rich UI experience. For Python files, this is done by calling the
lmnr Python package.#### Required Command Interface
The Python
lmnr package must implement the following CLI command:`bash
lmnr discover --file [--function ]
OR
lmnr discover --module [--function ]
`Arguments:
-
--file : Path to the Python file containing rollout functions (script mode)
- --module : Python module path like src.myfile (module mode)
- --function : (Optional) Specific function name to discover. If omitted, discover all.Note: Exactly one of
--file or --module must be provided.Output Format (JSON to stdout):
`json
{
"name": "my_agent",
"params": [
{
"name": "query",
"type": "string"
},
{
"name": "max_tokens",
"type": "number"
}
]
}
`Exit Codes:
-
0: Success
- Non-zero: Error (stderr contains error message)#### Python Execution Modes
Python code can be executed in two modes, affecting how imports work:
Script Mode (default):
`bash
npx lmnr-cli dev src/myfile.py
Internally executes as: python src/myfile.py
Discovery uses: lmnr discover --file src/myfile.py
`- Works with relative imports:
from .utils import helper
- File is executed as a script
- Best for standalone files and simple packagesModule Mode:
`bash
npx lmnr-cli dev -m src.myfile
Internally executes as: python -m src.myfile
Discovery uses: lmnr discover --module src.myfile
`- Requires absolute imports:
from src.utils import helper
- Module is executed via Python's -m flag
- Best for packages with complex import structures
- Requires proper __init__.py files in package directoriesExamples:
`bash
Script mode - use file path with relative imports
npx lmnr-cli dev agents/customer_support.pyModule mode - use module syntax with absolute imports
npx lmnr-cli dev -m agents.customer_supportSpecify a particular function
npx lmnr-cli dev -m agents.customer_support --function run_agent
`$3
The Python
lmnr package must implement a worker that executes rollout functions:`bash
python -m lmnr.cli.worker
`The worker should:
1. Read JSON configuration from stdin containing either
filePath (script mode) or modulePath (module mode)
2. Set environment variables from config
3. Initialize the lmnr SDK
4. Load and execute the specified function based on the mode
5. Send protocol messages to stdout (see Worker Protocol)
6. Exit with appropriate codeWorker Configuration:
The CLI will pass either:
-
filePath: Path to Python file (script mode)
- modulePath: Python module path (module mode)The worker must detect which field is present and execute accordingly.
See the Worker Protocol section for detailed communication protocol.
Development
`bash
Install dependencies
pnpm installBuild
pnpm buildRun locally
node dist/index.cjs dev examples/agent.ts
``Apache-2.0