feat(cli): add execution_mode config setting for local/remote/auto preference
Add execution_mode enum (local/remote/auto) to config for persistent control over command execution behavior. Removes --local/--remote flags from commands to simplify user workflow - no need to check server connection status manually. Changes: - config.zig: Add ExecutionMode enum, execution_mode field, parsing/serialization - mode.zig: Update detect() to check execution_mode == .local - init.zig: Add --mode flag (local/remote/auto) for setting during init - info.zig: Use config execution_mode, removed --local/--remote flags - run.zig: Use config execution_mode, removed --local/--remote flags - exec/mod.zig: Use config execution_mode, removed --local/--remote flags Priority order for determining execution mode: 1. Config setting (execution_mode: local/remote/auto) 2. Auto-detect only if config is 'auto' Users set mode once during init: ml init --mode=local # Always use local ml init --mode=remote # Always use remote ml init --mode=auto # Auto-detect (default)
This commit is contained in:
parent
cf8115c670
commit
a36a5e4522
6 changed files with 57 additions and 55 deletions
|
|
@ -29,8 +29,6 @@ pub const ExecOptions = struct {
|
|||
pub fn run(allocator: std.mem.Allocator, args: []const []const u8) !void {
|
||||
var flags = core.flags.CommonFlags{};
|
||||
var priority: u8 = 5;
|
||||
var force_local = false;
|
||||
var force_remote = false;
|
||||
|
||||
// Find "--" separator
|
||||
var sep_index: ?usize = null;
|
||||
|
|
@ -73,10 +71,6 @@ pub fn run(allocator: std.mem.Allocator, args: []const []const u8) !void {
|
|||
i += 1;
|
||||
} else if (std.mem.eql(u8, arg, "--dry-run")) {
|
||||
options.dry_run = true;
|
||||
} else if (std.mem.eql(u8, arg, "--local")) {
|
||||
force_local = true;
|
||||
} else if (std.mem.eql(u8, arg, "--remote")) {
|
||||
force_remote = true;
|
||||
} else if (std.mem.eql(u8, arg, "--hypothesis") and i + 1 < pre.len) {
|
||||
options.hypothesis = pre[i + 1];
|
||||
i += 1;
|
||||
|
|
@ -128,18 +122,16 @@ pub fn run(allocator: std.mem.Allocator, args: []const []const u8) !void {
|
|||
mut.deinit(allocator);
|
||||
}
|
||||
|
||||
// Determine execution mode
|
||||
// Determine execution mode: config > auto-detect
|
||||
var exec_mode: ExecMode = undefined;
|
||||
|
||||
if (force_local) {
|
||||
if (cfg.execution_mode == .local) {
|
||||
exec_mode = .local;
|
||||
} else if (force_remote) {
|
||||
} else if (cfg.execution_mode == .remote) {
|
||||
exec_mode = .remote;
|
||||
} else {
|
||||
// Auto-detect
|
||||
const mode_result = try mode.detect(allocator, cfg);
|
||||
exec_mode = if (mode.isOnline(mode_result.mode)) .remote else .local;
|
||||
|
||||
if (mode_result.warning) |warn| {
|
||||
std.log.info("{s}", .{warn});
|
||||
}
|
||||
|
|
@ -182,10 +174,8 @@ fn printUsage() !void {
|
|||
\\ --gpu <n> GPU devices requested (default: 0)
|
||||
\\ --gpu-memory <spec> GPU memory spec
|
||||
\\
|
||||
\\Execution mode:
|
||||
\\ --local Force local execution
|
||||
\\ --remote Force remote (fail if offline)
|
||||
\\ (auto-detect if neither flag set)
|
||||
\\Execution mode is controlled by execution_mode setting in config.
|
||||
\\Use 'ml init --mode=local|remote|auto' to change.
|
||||
\\
|
||||
\\Research context:
|
||||
\\ --hypothesis <text> What you're testing
|
||||
|
|
|
|||
|
|
@ -10,16 +10,12 @@ const common = @import("common.zig");
|
|||
pub const Options = struct {
|
||||
json: bool = false,
|
||||
base: ?[]const u8 = null,
|
||||
local: bool = false,
|
||||
remote: bool = false,
|
||||
};
|
||||
|
||||
pub fn run(allocator: std.mem.Allocator, args: []const []const u8) !void {
|
||||
var flags = core.flags.CommonFlags{};
|
||||
var base: ?[]const u8 = null;
|
||||
var target_path: ?[]const u8 = null;
|
||||
var force_local = false;
|
||||
var force_remote = false;
|
||||
|
||||
var i: usize = 0;
|
||||
while (i < args.len) : (i += 1) {
|
||||
|
|
@ -29,10 +25,6 @@ pub fn run(allocator: std.mem.Allocator, args: []const []const u8) !void {
|
|||
} else if (std.mem.eql(u8, arg, "--base") and i + 1 < args.len) {
|
||||
base = args[i + 1];
|
||||
i += 1;
|
||||
} else if (std.mem.eql(u8, arg, "--local")) {
|
||||
force_local = true;
|
||||
} else if (std.mem.eql(u8, arg, "--remote")) {
|
||||
force_remote = true;
|
||||
} else if (std.mem.startsWith(u8, arg, "--help")) {
|
||||
return printUsage();
|
||||
} else if (std.mem.startsWith(u8, arg, "--")) {
|
||||
|
|
@ -57,9 +49,8 @@ pub fn run(allocator: std.mem.Allocator, args: []const []const u8) !void {
|
|||
mut_cfg.deinit(allocator);
|
||||
}
|
||||
|
||||
// Determine execution mode
|
||||
const mode_result = try mode.detect(allocator, cfg);
|
||||
const use_remote = if (force_local) false else if (force_remote) true else mode.isOnline(mode_result.mode);
|
||||
// Determine execution mode: config > auto-detect
|
||||
const use_remote = if (cfg.execution_mode == .local) false else if (cfg.execution_mode == .remote) true else mode.isOnline((try mode.detect(allocator, cfg)).mode);
|
||||
|
||||
if (use_remote) {
|
||||
// Try remote query first
|
||||
|
|
@ -226,12 +217,10 @@ fn displayRunInfo(allocator: std.mem.Allocator, root: std.json.ObjectMap, manife
|
|||
|
||||
fn printUsage() !void {
|
||||
std.debug.print("Usage:\n", .{});
|
||||
std.debug.print("\tml info <run_dir_or_manifest_path_or_id> [--json] [--base <path>] [--local] [--remote]\n", .{});
|
||||
std.debug.print("\tml info <run_dir_or_manifest_path_or_id> [--json] [--base <path>]\n", .{});
|
||||
std.debug.print("\nOptions:\n", .{});
|
||||
std.debug.print("\t--json\t\tOutput machine-readable JSON\n", .{});
|
||||
std.debug.print("\t--base <path>\tBase path for resolving run manifests\n", .{});
|
||||
std.debug.print("\t--local\t\tForce local manifest lookup\n", .{});
|
||||
std.debug.print("\t--remote\tForce remote server query (fails if offline)\n", .{});
|
||||
}
|
||||
|
||||
test "resolveManifestPath uses run_manifest.json for directories" {
|
||||
|
|
|
|||
|
|
@ -20,10 +20,22 @@ pub fn run(allocator: std.mem.Allocator, args: []const []const u8) !void {
|
|||
const cli_artifact_path = core.flags.parseKVFlag(remaining.items, "artifact-path");
|
||||
const cli_sync_uri = core.flags.parseKVFlag(remaining.items, "sync-uri");
|
||||
const force_local = core.flags.parseBoolFlag(remaining.items, "local");
|
||||
const cli_mode = core.flags.parseKVFlag(remaining.items, "mode");
|
||||
|
||||
var cfg = try Config.loadWithOverrides(allocator, cli_tracking_uri, cli_artifact_path, cli_sync_uri);
|
||||
defer cfg.deinit(allocator);
|
||||
|
||||
// Apply mode preference from --mode flag
|
||||
if (cli_mode) |mode| {
|
||||
if (std.mem.eql(u8, mode, "local")) {
|
||||
cfg.execution_mode = .local;
|
||||
} else if (std.mem.eql(u8, mode, "remote")) {
|
||||
cfg.execution_mode = .remote;
|
||||
} else {
|
||||
cfg.execution_mode = .auto;
|
||||
}
|
||||
}
|
||||
|
||||
// Print resolved config
|
||||
std.debug.print("Resolved config:\n", .{});
|
||||
std.debug.print("\ttracking_uri = {s}", .{cfg.tracking_uri});
|
||||
|
|
@ -42,6 +54,11 @@ pub fn run(allocator: std.mem.Allocator, args: []const []const u8) !void {
|
|||
std.debug.print("\n", .{});
|
||||
}
|
||||
std.debug.print("\tsync_uri = {s}\n", .{if (cfg.sync_uri.len > 0) cfg.sync_uri else "(not set)"});
|
||||
std.debug.print("\texecution_mode = {s}\n", .{switch (cfg.execution_mode) {
|
||||
.local => "local",
|
||||
.remote => "remote",
|
||||
.auto => "auto",
|
||||
}});
|
||||
std.debug.print("\n", .{});
|
||||
|
||||
// Default path: create config only (no DB speculatively)
|
||||
|
|
@ -102,6 +119,7 @@ fn printUsage() void {
|
|||
std.debug.print("Initialize FetchML configuration\n\n", .{});
|
||||
std.debug.print("Options:\n", .{});
|
||||
std.debug.print("\t--local\t\t\tCreate local database now (default: config only)\n", .{});
|
||||
std.debug.print("\t--mode MODE\t\tDefault execution mode: local, remote, or auto (default: auto)\n", .{});
|
||||
std.debug.print("\t--tracking-uri URI\tSQLite database path (e.g., sqlite://./fetch_ml.db)\n", .{});
|
||||
std.debug.print("\t--artifact-path PATH\tArtifacts directory (default: ./experiments/)\n", .{});
|
||||
std.debug.print("\t--sync-uri URI\t\tServer to sync with (e.g., wss://ml.company.com/ws)\n", .{});
|
||||
|
|
|
|||
|
|
@ -32,8 +32,6 @@ pub const RunOptions = struct {
|
|||
/// Unified run command - transparently handles local and remote execution
|
||||
pub fn execute(allocator: std.mem.Allocator, args: []const []const u8) !void {
|
||||
var flags = core.flags.CommonFlags{};
|
||||
var force_local = false;
|
||||
var force_remote = false;
|
||||
|
||||
// Find "--" separator
|
||||
var sep_index: ?usize = null;
|
||||
|
|
@ -80,10 +78,6 @@ pub fn execute(allocator: std.mem.Allocator, args: []const []const u8) !void {
|
|||
options.validate = true;
|
||||
} else if (std.mem.eql(u8, arg, "--explain")) {
|
||||
options.explain = true;
|
||||
} else if (std.mem.eql(u8, arg, "--local")) {
|
||||
force_local = true;
|
||||
} else if (std.mem.eql(u8, arg, "--remote")) {
|
||||
force_remote = true;
|
||||
} else if (std.mem.eql(u8, arg, "--force")) {
|
||||
options.force = true;
|
||||
} else if (std.mem.eql(u8, arg, "--hypothesis") and i + 1 < pre.len) {
|
||||
|
|
@ -135,12 +129,12 @@ pub fn execute(allocator: std.mem.Allocator, args: []const []const u8) !void {
|
|||
mut.deinit(allocator);
|
||||
}
|
||||
|
||||
// Determine execution mode
|
||||
// Determine execution mode: config > auto-detect
|
||||
var run_mode: RunMode = undefined;
|
||||
|
||||
if (force_local) {
|
||||
if (cfg.execution_mode == .local) {
|
||||
run_mode = .local;
|
||||
} else if (force_remote) {
|
||||
} else if (cfg.execution_mode == .remote) {
|
||||
run_mode = .remote;
|
||||
} else {
|
||||
const mode_result = try mode.detect(allocator, cfg);
|
||||
|
|
@ -203,13 +197,12 @@ fn explainJob(allocator: std.mem.Allocator, job_name: []const u8, options: *cons
|
|||
fn printUsage() !void {
|
||||
std.debug.print("Usage: ml run <job_name> [options] [-- <args>]\n", .{});
|
||||
std.debug.print("\nUnified run command - handles both local and remote execution.\n", .{});
|
||||
std.debug.print("Execution mode is controlled by execution_mode setting in config.\n", .{});
|
||||
std.debug.print("\nOptions:\n", .{});
|
||||
std.debug.print(" --priority <1-10> Job priority (default: 5)\n", .{});
|
||||
std.debug.print(" --cpu <n> CPU cores requested (default: 1)\n", .{});
|
||||
std.debug.print(" --memory <n> Memory GB requested (default: 4)\n", .{});
|
||||
std.debug.print(" --gpu <n> GPU devices requested (default: 0)\n", .{});
|
||||
std.debug.print(" --local Force local execution\n", .{});
|
||||
std.debug.print(" --remote Force remote execution\n", .{});
|
||||
std.debug.print(" --dry-run Show what would happen\n", .{});
|
||||
std.debug.print(" --validate Validate job without running\n", .{});
|
||||
std.debug.print(" --explain Explain what will happen\n", .{});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,12 @@
|
|||
const std = @import("std");
|
||||
const security = @import("security.zig");
|
||||
|
||||
pub const ExecutionMode = enum {
|
||||
local,
|
||||
remote,
|
||||
auto,
|
||||
};
|
||||
|
||||
pub const ExperimentConfig = struct {
|
||||
name: []const u8,
|
||||
entrypoint: []const u8,
|
||||
|
|
@ -15,8 +21,6 @@ pub const Config = struct {
|
|||
artifact_path: []const u8,
|
||||
// Sync target URI (for pushing local runs to server)
|
||||
sync_uri: []const u8,
|
||||
// Force local mode regardless of server config
|
||||
force_local: bool,
|
||||
// Experiment configuration ([experiment] section)
|
||||
experiment: ?ExperimentConfig,
|
||||
|
||||
|
|
@ -33,6 +37,9 @@ pub const Config = struct {
|
|||
default_gpu: u8,
|
||||
default_gpu_memory: ?[]const u8,
|
||||
|
||||
// Execution mode preference (local, remote, auto)
|
||||
execution_mode: ExecutionMode,
|
||||
|
||||
// CLI behavior defaults
|
||||
default_dry_run: bool,
|
||||
default_validate: bool,
|
||||
|
|
@ -135,7 +142,6 @@ pub const Config = struct {
|
|||
.tracking_uri = try allocator.dupe(u8, "sqlite://./fetch_ml.db"),
|
||||
.artifact_path = try allocator.dupe(u8, "./experiments/"),
|
||||
.sync_uri = try allocator.dupe(u8, ""),
|
||||
.force_local = false,
|
||||
.experiment = null,
|
||||
.worker_host = try allocator.dupe(u8, ""),
|
||||
.worker_user = try allocator.dupe(u8, ""),
|
||||
|
|
@ -146,6 +152,7 @@ pub const Config = struct {
|
|||
.default_memory = 8,
|
||||
.default_gpu = 0,
|
||||
.default_gpu_memory = null,
|
||||
.execution_mode = .auto,
|
||||
.default_dry_run = false,
|
||||
.default_validate = false,
|
||||
.default_json = false,
|
||||
|
|
@ -162,7 +169,6 @@ pub const Config = struct {
|
|||
.tracking_uri = "",
|
||||
.artifact_path = "",
|
||||
.sync_uri = "",
|
||||
.force_local = false,
|
||||
.experiment = null,
|
||||
.worker_host = "",
|
||||
.worker_user = "",
|
||||
|
|
@ -173,6 +179,7 @@ pub const Config = struct {
|
|||
.default_memory = 8,
|
||||
.default_gpu = 0,
|
||||
.default_gpu_memory = null,
|
||||
.execution_mode = .auto,
|
||||
.default_dry_run = false,
|
||||
.default_validate = false,
|
||||
.default_json = false,
|
||||
|
|
@ -219,8 +226,6 @@ pub const Config = struct {
|
|||
config.artifact_path = try allocator.dupe(u8, value);
|
||||
} else if (std.mem.eql(u8, key, "sync_uri")) {
|
||||
config.sync_uri = try allocator.dupe(u8, value);
|
||||
} else if (std.mem.eql(u8, key, "force_local")) {
|
||||
config.force_local = std.mem.eql(u8, value, "true");
|
||||
} else if (std.mem.eql(u8, key, "worker_host")) {
|
||||
config.worker_host = try allocator.dupe(u8, value);
|
||||
} else if (std.mem.eql(u8, key, "worker_user")) {
|
||||
|
|
@ -247,8 +252,14 @@ pub const Config = struct {
|
|||
config.default_validate = std.mem.eql(u8, value, "true");
|
||||
} else if (std.mem.eql(u8, key, "default_json")) {
|
||||
config.default_json = std.mem.eql(u8, value, "true");
|
||||
} else if (std.mem.eql(u8, key, "default_priority")) {
|
||||
config.default_priority = try std.fmt.parseInt(u8, value, 10);
|
||||
} else if (std.mem.eql(u8, key, "execution_mode")) {
|
||||
if (std.mem.eql(u8, value, "local")) {
|
||||
config.execution_mode = .local;
|
||||
} else if (std.mem.eql(u8, value, "remote")) {
|
||||
config.execution_mode = .remote;
|
||||
} else {
|
||||
config.execution_mode = .auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -291,7 +302,6 @@ pub const Config = struct {
|
|||
\\tracking_uri = "{s}"
|
||||
\\artifact_path = "{s}"
|
||||
\\sync_uri = "{s}"
|
||||
\\force_local = {s}
|
||||
\\{s}
|
||||
\\# Server config (for runner mode)
|
||||
\\worker_host = "{s}"
|
||||
|
|
@ -306,6 +316,7 @@ pub const Config = struct {
|
|||
\\default_gpu = {d}
|
||||
\\{s}
|
||||
\\# CLI behavior defaults
|
||||
\\execution_mode = "{s}"
|
||||
\\default_dry_run = {s}
|
||||
\\default_validate = {s}
|
||||
\\default_json = {s}
|
||||
|
|
@ -315,7 +326,6 @@ pub const Config = struct {
|
|||
self.tracking_uri,
|
||||
self.artifact_path,
|
||||
self.sync_uri,
|
||||
if (self.force_local) "true" else "false",
|
||||
if (self.experiment) |exp| try std.fmt.allocPrint(allocator,
|
||||
\\n[experiment]\nname = "{s}"\nentrypoint = "{s}"\n
|
||||
, .{ exp.name, exp.entrypoint }) else "",
|
||||
|
|
@ -330,6 +340,11 @@ pub const Config = struct {
|
|||
if (self.default_gpu_memory) |gpu_mem| try std.fmt.allocPrint(allocator,
|
||||
\\default_gpu_memory = "{s}"\n
|
||||
, .{gpu_mem}) else "",
|
||||
switch (self.execution_mode) {
|
||||
.local => "local",
|
||||
.remote => "remote",
|
||||
.auto => "auto",
|
||||
},
|
||||
if (self.default_dry_run) "true" else "false",
|
||||
if (self.default_validate) "true" else "false",
|
||||
if (self.default_json) "true" else "false",
|
||||
|
|
@ -367,9 +382,6 @@ pub const Config = struct {
|
|||
if (other.sync_uri.len > 0) {
|
||||
self.sync_uri = other.sync_uri;
|
||||
}
|
||||
if (other.force_local) {
|
||||
self.force_local = other.force_local;
|
||||
}
|
||||
if (other.experiment) |exp| {
|
||||
if (self.experiment == null) {
|
||||
self.experiment = exp;
|
||||
|
|
|
|||
|
|
@ -32,8 +32,8 @@ pub fn detect(allocator: std.mem.Allocator, cfg: Config) !DetectionResult {
|
|||
}
|
||||
}
|
||||
|
||||
// Priority 2: force_local in config
|
||||
if (cfg.force_local) {
|
||||
// Priority 2: execution_mode in config
|
||||
if (cfg.execution_mode == .local) {
|
||||
return .{ .mode = .offline, .warning = null };
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue