diff --git a/cmd/ai/cmd_agent.go b/cmd/ai/cmd_agent.go index 9091ddc3..3c2b3729 100644 --- a/cmd/ai/cmd_agent.go +++ b/cmd/ai/cmd_agent.go @@ -6,7 +6,9 @@ import ( "os/exec" "path/filepath" "strings" + "time" + agentic "forge.lthn.ai/core/go-agentic" "forge.lthn.ai/core/go-scm/agentci" "forge.lthn.ai/core/go/pkg/cli" "forge.lthn.ai/core/go/pkg/config" @@ -25,6 +27,7 @@ func AddAgentCommands(parent *cli.Command) { agentCmd.AddCommand(agentLogsCmd()) agentCmd.AddCommand(agentSetupCmd()) agentCmd.AddCommand(agentRemoveCmd()) + agentCmd.AddCommand(agentFleetCmd()) parent.AddCommand(agentCmd) } @@ -321,6 +324,76 @@ func agentRemoveCmd() *cli.Command { } } +func agentFleetCmd() *cli.Command { + cmd := &cli.Command{ + Use: "fleet", + Short: "Show fleet status from the go-agentic registry", + RunE: func(cmd *cli.Command, args []string) error { + workDir, _ := cmd.Flags().GetString("work-dir") + if workDir == "" { + home, _ := os.UserHomeDir() + workDir = filepath.Join(home, defaultWorkDir) + } + dbPath := filepath.Join(workDir, "registry.db") + + if _, err := os.Stat(dbPath); os.IsNotExist(err) { + fmt.Println(dimStyle.Render("No registry found. Start a dispatch watcher first: core ai dispatch watch")) + return nil + } + + registry, err := agentic.NewSQLiteRegistry(dbPath) + if err != nil { + return fmt.Errorf("failed to open registry: %w", err) + } + defer registry.Close() + + // Reap stale agents (no heartbeat for 10 minutes). + reaped := registry.Reap(10 * time.Minute) + if len(reaped) > 0 { + for _, id := range reaped { + fmt.Printf(" Reaped stale agent: %s\n", dimStyle.Render(id)) + } + fmt.Println() + } + + agents := registry.List() + if len(agents) == 0 { + fmt.Println(dimStyle.Render("No agents registered.")) + return nil + } + + table := cli.NewTable("ID", "STATUS", "LOAD", "LAST HEARTBEAT", "CAPABILITIES") + for _, a := range agents { + status := dimStyle.Render(string(a.Status)) + switch a.Status { + case agentic.AgentAvailable: + status = successStyle.Render("available") + case agentic.AgentBusy: + status = taskPriorityMediumStyle.Render("busy") + case agentic.AgentOffline: + status = errorStyle.Render("offline") + } + + load := fmt.Sprintf("%d/%d", a.CurrentLoad, a.MaxLoad) + hb := a.LastHeartbeat.Format("15:04:05") + ago := time.Since(a.LastHeartbeat).Truncate(time.Second) + hbStr := fmt.Sprintf("%s (%s ago)", hb, ago) + + caps := "-" + if len(a.Capabilities) > 0 { + caps = strings.Join(a.Capabilities, ", ") + } + + table.AddRow(a.ID, status, load, hbStr, caps) + } + table.Render() + return nil + }, + } + cmd.Flags().String("work-dir", "", "Working directory (default: ~/ai-work)") + return cmd +} + // findSetupScript looks for agent-setup.sh in common locations. func findSetupScript() string { exe, _ := os.Executable() diff --git a/cmd/ai/cmd_dispatch.go b/cmd/ai/cmd_dispatch.go index 326ae6d0..3aa61f8e 100644 --- a/cmd/ai/cmd_dispatch.go +++ b/cmd/ai/cmd_dispatch.go @@ -18,6 +18,8 @@ import ( "forge.lthn.ai/core/go/pkg/cli" "forge.lthn.ai/core/go/pkg/log" + + agentic "forge.lthn.ai/core/go-agentic" ) // AddDispatchCommands registers the 'dispatch' subcommand group under 'ai'. @@ -125,30 +127,45 @@ func dispatchWatchCmd() *cli.Command { RunE: func(cmd *cli.Command, args []string) error { workDir, _ := cmd.Flags().GetString("work-dir") interval, _ := cmd.Flags().GetDuration("interval") + agentID, _ := cmd.Flags().GetString("agent-id") paths := getPaths(workDir) if err := ensureDispatchDirs(paths); err != nil { return err } - log.Info("Starting dispatch watcher", "dir", paths.root, "interval", interval) + // Register this agent in the go-agentic registry. + registry, events, cleanup := registerAgent(agentID, paths) + if cleanup != nil { + defer cleanup() + } + + log.Info("Starting dispatch watcher", "dir", paths.root, "interval", interval, "agent", agentID) ctx, cancel := context.WithCancel(context.Background()) defer cancel() sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) + // Heartbeat loop — keeps agent status fresh. + if registry != nil { + go heartbeatLoop(ctx, registry, agentID, interval/2) + } + ticker := time.NewTicker(interval) defer ticker.Stop() - runCycle(paths) + runCycleWithEvents(paths, registry, events, agentID) for { select { case <-ticker.C: - runCycle(paths) + runCycleWithEvents(paths, registry, events, agentID) case <-sigChan: log.Info("Shutting down watcher...") + if registry != nil { + _ = registry.Deregister(agentID) + } return nil case <-ctx.Done(): return nil @@ -158,9 +175,95 @@ func dispatchWatchCmd() *cli.Command { } cmd.Flags().String("work-dir", "", "Working directory (default: ~/ai-work)") cmd.Flags().Duration("interval", 5*time.Minute, "Polling interval") + cmd.Flags().String("agent-id", defaultAgentID(), "Agent identifier for registry") return cmd } +// defaultAgentID returns a sensible agent ID from hostname. +func defaultAgentID() string { + host, _ := os.Hostname() + if host == "" { + return "unknown" + } + return host +} + +// registerAgent creates a SQLite registry and registers this agent. +// Returns the registry, event emitter, and a cleanup function. +func registerAgent(agentID string, paths runnerPaths) (agentic.AgentRegistry, agentic.EventEmitter, func()) { + dbPath := filepath.Join(paths.root, "registry.db") + registry, err := agentic.NewSQLiteRegistry(dbPath) + if err != nil { + log.Warn("Failed to create agent registry", "error", err, "path", dbPath) + return nil, nil, nil + } + + info := agentic.AgentInfo{ + ID: agentID, + Name: agentID, + Status: agentic.AgentAvailable, + LastHeartbeat: time.Now().UTC(), + MaxLoad: 1, + } + if err := registry.Register(info); err != nil { + log.Warn("Failed to register agent", "error", err) + } else { + log.Info("Agent registered", "id", agentID) + } + + events := agentic.NewChannelEmitter(64) + + // Drain events to log. + go func() { + for ev := range events.Events() { + log.Debug("Event", "type", string(ev.Type), "task", ev.TaskID, "agent", ev.AgentID) + } + }() + + return registry, events, func() { + events.Close() + } +} + +// heartbeatLoop sends periodic heartbeats to keep the agent status fresh. +func heartbeatLoop(ctx context.Context, registry agentic.AgentRegistry, agentID string, interval time.Duration) { + if interval < 30*time.Second { + interval = 30 * time.Second + } + ticker := time.NewTicker(interval) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + _ = registry.Heartbeat(agentID) + } + } +} + +// runCycleWithEvents wraps runCycle with registry status updates and event emission. +func runCycleWithEvents(paths runnerPaths, registry agentic.AgentRegistry, events agentic.EventEmitter, agentID string) { + if registry != nil { + // Set busy while processing. + if agent, err := registry.Get(agentID); err == nil { + agent.Status = agentic.AgentBusy + _ = registry.Register(agent) + } + } + + runCycle(paths) + + if registry != nil { + // Back to available. + if agent, err := registry.Get(agentID); err == nil { + agent.Status = agentic.AgentAvailable + agent.LastHeartbeat = time.Now().UTC() + _ = registry.Register(agent) + } + } +} + func dispatchStatusCmd() *cli.Command { cmd := &cli.Command{ Use: "status", diff --git a/cmd/ai/cmd_git.go b/cmd/ai/cmd_git.go index ec11db23..da3c1c9b 100644 --- a/cmd/ai/cmd_git.go +++ b/cmd/ai/cmd_git.go @@ -10,7 +10,7 @@ import ( "strings" "time" - "forge.lthn.ai/core/go-ai/agentic" + "forge.lthn.ai/core/go-agentic" "forge.lthn.ai/core/go/pkg/cli" "forge.lthn.ai/core/go/pkg/i18n" ) diff --git a/cmd/ai/cmd_tasks.go b/cmd/ai/cmd_tasks.go index e4f9c53e..856c091a 100644 --- a/cmd/ai/cmd_tasks.go +++ b/cmd/ai/cmd_tasks.go @@ -9,7 +9,7 @@ import ( "strings" "time" - "forge.lthn.ai/core/go-ai/agentic" + "forge.lthn.ai/core/go-agentic" "forge.lthn.ai/core/go-ai/ai" "forge.lthn.ai/core/go/pkg/cli" "forge.lthn.ai/core/go/pkg/i18n" diff --git a/cmd/ai/cmd_updates.go b/cmd/ai/cmd_updates.go index a1eccfc4..48f407bf 100644 --- a/cmd/ai/cmd_updates.go +++ b/cmd/ai/cmd_updates.go @@ -6,7 +6,7 @@ import ( "context" "time" - "forge.lthn.ai/core/go-ai/agentic" + "forge.lthn.ai/core/go-agentic" "forge.lthn.ai/core/go-ai/ai" "forge.lthn.ai/core/go/pkg/cli" "forge.lthn.ai/core/go/pkg/i18n" diff --git a/cmd/daemon/cmd.go b/cmd/daemon/cmd.go index c6bb7bbb..45a3f1b4 100644 --- a/cmd/daemon/cmd.go +++ b/cmd/daemon/cmd.go @@ -3,7 +3,6 @@ package daemon import ( "context" - "encoding/json" "fmt" "net/http" "os" @@ -16,7 +15,6 @@ import ( "forge.lthn.ai/core/go/pkg/cli" "forge.lthn.ai/core/go/pkg/log" - "forge.lthn.ai/core/go/pkg/process" "forge.lthn.ai/core/go-ai/mcp" ) @@ -301,61 +299,33 @@ func runForeground(cfg Config) error { return fmt.Errorf("failed to start daemon: %w", err) } - // Create supervisor for managed services - sup := process.NewSupervisor(nil) // nil service — we only supervise Go functions - - // Register MCP server as a supervised service - sup.RegisterFunc(process.GoSpec{ - Name: "mcp", - Func: func(ctx context.Context) error { - return startMCP(ctx, mcpSvc, cfg) - }, - Restart: process.RestartPolicy{ - Delay: 3 * time.Second, - MaxRestarts: -1, // Unlimited restarts - }, - }) - - // Start supervised services - sup.Start() - // Mark as ready daemon.SetReady(true) - // Add supervisor status to health checks - daemon.AddHealthCheck(func() error { - statuses := sup.Statuses() - for name, status := range statuses { - if !status.Running { - return fmt.Errorf("service %s is not running (restarts: %d)", name, status.RestartCount) - } - } - return nil - }) + // Start MCP server in a goroutine + ctx := cli.Context() + mcpErr := make(chan error, 1) + go func() { + mcpErr <- startMCP(ctx, mcpSvc, cfg) + }() log.Info("Daemon ready", "pid", os.Getpid(), "health", daemon.HealthAddr(), - "services", strings.Join(sup.UnitNames(), ", "), + "services", "mcp", ) - // Print supervised service status as JSON for machine consumption - statuses := sup.Statuses() - if data, err := json.Marshal(statuses); err == nil { - log.Debug("Supervised services", "statuses", string(data)) + // Wait for shutdown signal or MCP error + select { + case <-ctx.Done(): + log.Info("Shutting down daemon") + case err := <-mcpErr: + if err != nil { + log.Error("MCP server exited", "error", err) + } } - // Get context that cancels on SIGINT/SIGTERM - ctx := cli.Context() - - // Wait for shutdown signal - <-ctx.Done() - log.Info("Shutting down daemon") - - // Stop supervised services first - sup.Stop() - - // Then stop the daemon (releases PID, stops health server) + // Stop the daemon (releases PID, stops health server) return daemon.Stop() } diff --git a/cmd/dev/cmd_bundles.go b/cmd/dev/cmd_bundles.go index 1be61307..87217133 100644 --- a/cmd/dev/cmd_bundles.go +++ b/cmd/dev/cmd_bundles.go @@ -3,7 +3,7 @@ package dev import ( "context" - "forge.lthn.ai/core/go-ai/agentic" + "forge.lthn.ai/core/go-agentic" "forge.lthn.ai/core/go/pkg/framework" "forge.lthn.ai/core/go-scm/git" ) diff --git a/cmd/dev/cmd_work.go b/cmd/dev/cmd_work.go index aa108817..53946083 100644 --- a/cmd/dev/cmd_work.go +++ b/cmd/dev/cmd_work.go @@ -7,7 +7,7 @@ import ( "sort" "strings" - "forge.lthn.ai/core/go-ai/agentic" + "forge.lthn.ai/core/go-agentic" "forge.lthn.ai/core/go/pkg/cli" "forge.lthn.ai/core/go-scm/git" "forge.lthn.ai/core/go/pkg/i18n" diff --git a/cmd/dev/service.go b/cmd/dev/service.go index 83f0ab1a..ab64cb1c 100644 --- a/cmd/dev/service.go +++ b/cmd/dev/service.go @@ -5,7 +5,7 @@ import ( "sort" "strings" - "forge.lthn.ai/core/go-ai/agentic" + "forge.lthn.ai/core/go-agentic" "forge.lthn.ai/core/go/pkg/cli" "forge.lthn.ai/core/go/pkg/framework" "forge.lthn.ai/core/go-scm/git" diff --git a/cmd/ml/cmd_agent.go b/cmd/ml/cmd_agent.go index b617a0fe..21fa678e 100644 --- a/cmd/ml/cmd_agent.go +++ b/cmd/ml/cmd_agent.go @@ -2,7 +2,7 @@ package ml import ( "forge.lthn.ai/core/go/pkg/cli" - "forge.lthn.ai/core/go-ai/ml" + "forge.lthn.ai/core/go-ml" ) var ( diff --git a/cmd/ml/cmd_approve.go b/cmd/ml/cmd_approve.go index a40f41e4..a7212df0 100644 --- a/cmd/ml/cmd_approve.go +++ b/cmd/ml/cmd_approve.go @@ -6,7 +6,7 @@ import ( "path/filepath" "forge.lthn.ai/core/go/pkg/cli" - "forge.lthn.ai/core/go-ai/ml" + "forge.lthn.ai/core/go-ml" ) var ( diff --git a/cmd/ml/cmd_benchmark.go b/cmd/ml/cmd_benchmark.go index 773f53ad..7290354a 100644 --- a/cmd/ml/cmd_benchmark.go +++ b/cmd/ml/cmd_benchmark.go @@ -12,7 +12,7 @@ import ( "sort" "time" - "forge.lthn.ai/core/go-ai/ml" + "forge.lthn.ai/core/go-ml" "forge.lthn.ai/core/go/pkg/cli" ) diff --git a/cmd/ml/cmd_chat.go b/cmd/ml/cmd_chat.go index f6322907..ac470361 100644 --- a/cmd/ml/cmd_chat.go +++ b/cmd/ml/cmd_chat.go @@ -12,7 +12,7 @@ import ( "strings" "time" - "forge.lthn.ai/core/go-ai/ml" + "forge.lthn.ai/core/go-ml" "forge.lthn.ai/core/go/pkg/cli" ) diff --git a/cmd/ml/cmd_consolidate.go b/cmd/ml/cmd_consolidate.go index 1b3eec5e..0e1cf20b 100644 --- a/cmd/ml/cmd_consolidate.go +++ b/cmd/ml/cmd_consolidate.go @@ -2,7 +2,7 @@ package ml import ( "forge.lthn.ai/core/go/pkg/cli" - "forge.lthn.ai/core/go-ai/ml" + "forge.lthn.ai/core/go-ml" ) var ( diff --git a/cmd/ml/cmd_convert.go b/cmd/ml/cmd_convert.go index 4e00620c..84c9b27e 100644 --- a/cmd/ml/cmd_convert.go +++ b/cmd/ml/cmd_convert.go @@ -4,7 +4,7 @@ import ( "fmt" "forge.lthn.ai/core/go/pkg/cli" - "forge.lthn.ai/core/go-ai/ml" + "forge.lthn.ai/core/go-ml" ) var ( diff --git a/cmd/ml/cmd_coverage.go b/cmd/ml/cmd_coverage.go index a4ae7765..5d3acf7f 100644 --- a/cmd/ml/cmd_coverage.go +++ b/cmd/ml/cmd_coverage.go @@ -5,7 +5,7 @@ import ( "os" "forge.lthn.ai/core/go/pkg/cli" - "forge.lthn.ai/core/go-ai/ml" + "forge.lthn.ai/core/go-ml" ) var coverageCmd = &cli.Command{ diff --git a/cmd/ml/cmd_expand.go b/cmd/ml/cmd_expand.go index 6ef1fe32..432aa3fa 100644 --- a/cmd/ml/cmd_expand.go +++ b/cmd/ml/cmd_expand.go @@ -6,7 +6,7 @@ import ( "os" "forge.lthn.ai/core/go/pkg/cli" - "forge.lthn.ai/core/go-ai/ml" + "forge.lthn.ai/core/go-ml" ) var ( diff --git a/cmd/ml/cmd_expand_status.go b/cmd/ml/cmd_expand_status.go index 5bd8b765..70df2bf2 100644 --- a/cmd/ml/cmd_expand_status.go +++ b/cmd/ml/cmd_expand_status.go @@ -5,7 +5,7 @@ import ( "os" "forge.lthn.ai/core/go/pkg/cli" - "forge.lthn.ai/core/go-ai/ml" + "forge.lthn.ai/core/go-ml" ) var expandStatusCmd = &cli.Command{ @@ -34,40 +34,32 @@ func runExpandStatus(cmd *cli.Command, args []string) error { fmt.Fprintln(os.Stdout, "==================================================") // Expansion prompts - total, pending, err := db.ExpansionPromptCounts() + total, pending, err := db.CountExpansionPrompts() if err != nil { fmt.Fprintln(os.Stdout, " Expansion prompts: not created (run: normalize)") return nil } fmt.Fprintf(os.Stdout, " Expansion prompts: %d total, %d pending\n", total, pending) - // Generated responses - generated, models, err := db.ExpansionRawCounts() - if err != nil { - generated = 0 + // Generated responses — query raw counts via SQL + generated := 0 + rows, err := db.QueryRows("SELECT count(*) AS n FROM expansion_raw") + if err != nil || len(rows) == 0 { fmt.Fprintln(os.Stdout, " Generated: 0 (run: core ml expand)") - } else if len(models) > 0 { - modelStr := "" - for i, m := range models { - if i > 0 { - modelStr += ", " - } - modelStr += fmt.Sprintf("%s: %d", m.Name, m.Count) - } - fmt.Fprintf(os.Stdout, " Generated: %d (%s)\n", generated, modelStr) } else { + if n, ok := rows[0]["n"]; ok { + generated = toInt(n) + } fmt.Fprintf(os.Stdout, " Generated: %d\n", generated) } - // Scored - scored, hPassed, jScored, jPassed, err := db.ExpansionScoreCounts() - if err != nil { + // Scored — query scoring counts via SQL + sRows, err := db.QueryRows("SELECT count(*) AS n FROM scoring_results WHERE suite = 'heuristic'") + if err != nil || len(sRows) == 0 { fmt.Fprintln(os.Stdout, " Scored: 0 (run: score --tier 1)") } else { - fmt.Fprintf(os.Stdout, " Heuristic scored: %d (%d passed)\n", scored, hPassed) - if jScored > 0 { - fmt.Fprintf(os.Stdout, " Judge scored: %d (%d passed)\n", jScored, jPassed) - } + scored := toInt(sRows[0]["n"]) + fmt.Fprintf(os.Stdout, " Heuristic scored: %d\n", scored) } // Pipeline progress @@ -77,7 +69,7 @@ func runExpandStatus(cmd *cli.Command, args []string) error { } // Golden set context - golden, err := db.GoldenSetCount() + golden, err := db.CountGoldenSet() if err == nil && golden > 0 { fmt.Fprintf(os.Stdout, "\n Golden set: %d / %d\n", golden, targetTotal) if generated > 0 { @@ -87,3 +79,17 @@ func runExpandStatus(cmd *cli.Command, args []string) error { return nil } + +// toInt converts an interface{} (typically from QueryRows) to int. +func toInt(v interface{}) int { + switch n := v.(type) { + case int: + return n + case int64: + return int(n) + case float64: + return int(n) + default: + return 0 + } +} diff --git a/cmd/ml/cmd_export.go b/cmd/ml/cmd_export.go index ebf08e06..2ce2b60a 100644 --- a/cmd/ml/cmd_export.go +++ b/cmd/ml/cmd_export.go @@ -5,7 +5,7 @@ import ( "os" "forge.lthn.ai/core/go/pkg/cli" - "forge.lthn.ai/core/go-ai/ml" + "forge.lthn.ai/core/go-ml" ) var ( diff --git a/cmd/ml/cmd_gguf.go b/cmd/ml/cmd_gguf.go index b48d9c23..8b1f53b4 100644 --- a/cmd/ml/cmd_gguf.go +++ b/cmd/ml/cmd_gguf.go @@ -4,7 +4,7 @@ import ( "fmt" "forge.lthn.ai/core/go/pkg/cli" - "forge.lthn.ai/core/go-ai/ml" + "forge.lthn.ai/core/go-ml" ) var ( diff --git a/cmd/ml/cmd_import.go b/cmd/ml/cmd_import.go index 11d3ce74..03fb7dd0 100644 --- a/cmd/ml/cmd_import.go +++ b/cmd/ml/cmd_import.go @@ -6,7 +6,7 @@ import ( "path/filepath" "forge.lthn.ai/core/go/pkg/cli" - "forge.lthn.ai/core/go-ai/ml" + "forge.lthn.ai/core/go-ml" ) var importCmd = &cli.Command{ diff --git a/cmd/ml/cmd_ingest.go b/cmd/ml/cmd_ingest.go index efac7e99..f071a2a6 100644 --- a/cmd/ml/cmd_ingest.go +++ b/cmd/ml/cmd_ingest.go @@ -5,7 +5,7 @@ import ( "os" "forge.lthn.ai/core/go/pkg/cli" - "forge.lthn.ai/core/go-ai/ml" + "forge.lthn.ai/core/go-ml" ) var ingestCmd = &cli.Command{ diff --git a/cmd/ml/cmd_inventory.go b/cmd/ml/cmd_inventory.go index d3b8970c..eed4ff6a 100644 --- a/cmd/ml/cmd_inventory.go +++ b/cmd/ml/cmd_inventory.go @@ -5,7 +5,7 @@ import ( "os" "forge.lthn.ai/core/go/pkg/cli" - "forge.lthn.ai/core/go-ai/ml" + "forge.lthn.ai/core/go-ml" ) var inventoryCmd = &cli.Command{ diff --git a/cmd/ml/cmd_lesson.go b/cmd/ml/cmd_lesson.go index ba2a3e7d..4246c076 100644 --- a/cmd/ml/cmd_lesson.go +++ b/cmd/ml/cmd_lesson.go @@ -13,7 +13,7 @@ import ( "strings" "time" - "forge.lthn.ai/core/go-ai/ml" + "forge.lthn.ai/core/go-ml" "forge.lthn.ai/core/go/pkg/cli" "gopkg.in/yaml.v3" ) diff --git a/cmd/ml/cmd_live.go b/cmd/ml/cmd_live.go index 994399f5..5abef9ce 100644 --- a/cmd/ml/cmd_live.go +++ b/cmd/ml/cmd_live.go @@ -5,7 +5,7 @@ import ( "os" "forge.lthn.ai/core/go/pkg/cli" - "forge.lthn.ai/core/go-ai/ml" + "forge.lthn.ai/core/go-ml" ) const targetTotal = 15000 @@ -21,24 +21,27 @@ func runLive(cmd *cli.Command, args []string) error { influx := ml.NewInfluxClient(influxURL, influxDB) // Total completed generations - total, err := influx.QueryScalar("SELECT count(DISTINCT i) AS n FROM gold_gen") + totalRows, err := influx.QuerySQL("SELECT count(DISTINCT i) AS n FROM gold_gen") if err != nil { return fmt.Errorf("live: query total: %w", err) } + total := sqlScalar(totalRows) // Distinct domains and voices - domains, err := influx.QueryScalar("SELECT count(DISTINCT d) AS n FROM gold_gen") + domainRows, err := influx.QuerySQL("SELECT count(DISTINCT d) AS n FROM gold_gen") if err != nil { return fmt.Errorf("live: query domains: %w", err) } + domains := sqlScalar(domainRows) - voices, err := influx.QueryScalar("SELECT count(DISTINCT v) AS n FROM gold_gen") + voiceRows, err := influx.QuerySQL("SELECT count(DISTINCT v) AS n FROM gold_gen") if err != nil { return fmt.Errorf("live: query voices: %w", err) } + voices := sqlScalar(voiceRows) // Per-worker breakdown - workers, err := influx.QueryRows("SELECT w, count(DISTINCT i) AS n FROM gold_gen GROUP BY w ORDER BY n DESC") + workers, err := influx.QuerySQL("SELECT w, count(DISTINCT i) AS n FROM gold_gen GROUP BY w ORDER BY n DESC") if err != nil { return fmt.Errorf("live: query workers: %w", err) } @@ -66,3 +69,14 @@ func runLive(cmd *cli.Command, args []string) error { return nil } + +// sqlScalar extracts the first numeric value from a QuerySQL result. +func sqlScalar(rows []map[string]interface{}) int { + if len(rows) == 0 { + return 0 + } + for _, v := range rows[0] { + return toInt(v) + } + return 0 +} diff --git a/cmd/ml/cmd_metrics.go b/cmd/ml/cmd_metrics.go index 0438bb01..6bc6bcf2 100644 --- a/cmd/ml/cmd_metrics.go +++ b/cmd/ml/cmd_metrics.go @@ -5,7 +5,7 @@ import ( "os" "forge.lthn.ai/core/go/pkg/cli" - "forge.lthn.ai/core/go-ai/ml" + "forge.lthn.ai/core/go-ml" ) var metricsCmd = &cli.Command{ diff --git a/cmd/ml/cmd_normalize.go b/cmd/ml/cmd_normalize.go index 152e1415..4e4715bc 100644 --- a/cmd/ml/cmd_normalize.go +++ b/cmd/ml/cmd_normalize.go @@ -5,7 +5,7 @@ import ( "os" "forge.lthn.ai/core/go/pkg/cli" - "forge.lthn.ai/core/go-ai/ml" + "forge.lthn.ai/core/go-ml" ) var normalizeMinLen int diff --git a/cmd/ml/cmd_probe.go b/cmd/ml/cmd_probe.go index 779b3712..68ed39ce 100644 --- a/cmd/ml/cmd_probe.go +++ b/cmd/ml/cmd_probe.go @@ -7,7 +7,7 @@ import ( "os" "forge.lthn.ai/core/go/pkg/cli" - "forge.lthn.ai/core/go-ai/ml" + "forge.lthn.ai/core/go-ml" ) var ( diff --git a/cmd/ml/cmd_publish.go b/cmd/ml/cmd_publish.go index 54b89b4a..2f0ff6ff 100644 --- a/cmd/ml/cmd_publish.go +++ b/cmd/ml/cmd_publish.go @@ -2,7 +2,7 @@ package ml import ( "forge.lthn.ai/core/go/pkg/cli" - "forge.lthn.ai/core/go-ai/ml" + "forge.lthn.ai/core/go-ml" ) var ( diff --git a/cmd/ml/cmd_query.go b/cmd/ml/cmd_query.go index 185d3fcc..00d49070 100644 --- a/cmd/ml/cmd_query.go +++ b/cmd/ml/cmd_query.go @@ -7,7 +7,7 @@ import ( "strings" "forge.lthn.ai/core/go/pkg/cli" - "forge.lthn.ai/core/go-ai/ml" + "forge.lthn.ai/core/go-ml" ) var queryCmd = &cli.Command{ diff --git a/cmd/ml/cmd_sandwich.go b/cmd/ml/cmd_sandwich.go index 9fcad6d9..80acee4d 100644 --- a/cmd/ml/cmd_sandwich.go +++ b/cmd/ml/cmd_sandwich.go @@ -11,7 +11,7 @@ import ( "runtime" "time" - "forge.lthn.ai/core/go-ai/ml" + "forge.lthn.ai/core/go-ml" "forge.lthn.ai/core/go/pkg/cli" ) diff --git a/cmd/ml/cmd_score.go b/cmd/ml/cmd_score.go index cf603c44..7c0cc25a 100644 --- a/cmd/ml/cmd_score.go +++ b/cmd/ml/cmd_score.go @@ -6,7 +6,7 @@ import ( "time" "forge.lthn.ai/core/go/pkg/cli" - "forge.lthn.ai/core/go-ai/ml" + "forge.lthn.ai/core/go-ml" ) var ( diff --git a/cmd/ml/cmd_seed_influx.go b/cmd/ml/cmd_seed_influx.go index 779b6d52..30ece12e 100644 --- a/cmd/ml/cmd_seed_influx.go +++ b/cmd/ml/cmd_seed_influx.go @@ -5,7 +5,7 @@ import ( "os" "forge.lthn.ai/core/go/pkg/cli" - "forge.lthn.ai/core/go-ai/ml" + "forge.lthn.ai/core/go-ml" ) var seedInfluxCmd = &cli.Command{ diff --git a/cmd/ml/cmd_sequence.go b/cmd/ml/cmd_sequence.go index c1042c32..2b56c1af 100644 --- a/cmd/ml/cmd_sequence.go +++ b/cmd/ml/cmd_sequence.go @@ -11,7 +11,7 @@ import ( "strings" "time" - "forge.lthn.ai/core/go-ai/ml" + "forge.lthn.ai/core/go-ml" "forge.lthn.ai/core/go/pkg/cli" "gopkg.in/yaml.v3" ) diff --git a/cmd/ml/cmd_serve.go b/cmd/ml/cmd_serve.go index 180ced2a..737c7a18 100644 --- a/cmd/ml/cmd_serve.go +++ b/cmd/ml/cmd_serve.go @@ -15,7 +15,7 @@ import ( "time" "forge.lthn.ai/core/go/pkg/cli" - "forge.lthn.ai/core/go-ai/ml" + "forge.lthn.ai/core/go-ml" ) var serveCmd = &cli.Command{ diff --git a/cmd/ml/cmd_status.go b/cmd/ml/cmd_status.go index 987044a2..0c6c08f7 100644 --- a/cmd/ml/cmd_status.go +++ b/cmd/ml/cmd_status.go @@ -5,7 +5,7 @@ import ( "os" "forge.lthn.ai/core/go/pkg/cli" - "forge.lthn.ai/core/go-ai/ml" + "forge.lthn.ai/core/go-ml" ) var statusCmd = &cli.Command{ diff --git a/cmd/ml/cmd_train.go b/cmd/ml/cmd_train.go index 0123bf71..9df4bf0d 100644 --- a/cmd/ml/cmd_train.go +++ b/cmd/ml/cmd_train.go @@ -12,8 +12,8 @@ import ( "strings" "time" - "forge.lthn.ai/core/go-ai/ml" - "forge.lthn.ai/core/go-ai/mlx" + "forge.lthn.ai/core/go-ml" + "forge.lthn.ai/core/go-mlx" "forge.lthn.ai/core/go-ai/mlx/model" "forge.lthn.ai/core/go-ai/mlx/tokenizer" "forge.lthn.ai/core/go/pkg/cli" diff --git a/cmd/ml/cmd_worker.go b/cmd/ml/cmd_worker.go index 61a9125e..73767de3 100644 --- a/cmd/ml/cmd_worker.go +++ b/cmd/ml/cmd_worker.go @@ -4,7 +4,7 @@ import ( "time" "forge.lthn.ai/core/go/pkg/cli" - "forge.lthn.ai/core/go-ai/ml" + "forge.lthn.ai/core/go-ml" ) var ( diff --git a/cmd/ml/serve_backend_default.go b/cmd/ml/serve_backend_default.go index ab71a8d0..1e564b17 100644 --- a/cmd/ml/serve_backend_default.go +++ b/cmd/ml/serve_backend_default.go @@ -2,7 +2,7 @@ package ml -import "forge.lthn.ai/core/go-ai/ml" +import "forge.lthn.ai/core/go-ml" func createServeBackend() (ml.Backend, error) { return ml.NewHTTPBackend(apiURL, modelName), nil diff --git a/cmd/ml/serve_backend_mlx.go b/cmd/ml/serve_backend_mlx.go index d40e5bfb..271eaa95 100644 --- a/cmd/ml/serve_backend_mlx.go +++ b/cmd/ml/serve_backend_mlx.go @@ -6,7 +6,7 @@ import ( "fmt" "log/slog" - "forge.lthn.ai/core/go-ai/ml" + "forge.lthn.ai/core/go-ml" ) func createServeBackend() (ml.Backend, error) { diff --git a/cmd/rag/cmd_collections.go b/cmd/rag/cmd_collections.go index bfcb508a..32001418 100644 --- a/cmd/rag/cmd_collections.go +++ b/cmd/rag/cmd_collections.go @@ -6,7 +6,7 @@ import ( "forge.lthn.ai/core/go/pkg/cli" "forge.lthn.ai/core/go/pkg/i18n" - "forge.lthn.ai/core/go-ai/rag" + "forge.lthn.ai/core/go-rag" "github.com/spf13/cobra" ) @@ -74,8 +74,8 @@ func runCollections(cmd *cobra.Command, args []string) error { continue } fmt.Printf(" %s\n", cli.ValueStyle.Render(name)) - fmt.Printf(" Points: %d\n", info.PointsCount) - fmt.Printf(" Status: %s\n", info.Status.String()) + fmt.Printf(" Points: %d\n", info.PointCount) + fmt.Printf(" Status: %s\n", info.Status) fmt.Println() } else { fmt.Printf(" %s\n", name) diff --git a/cmd/rag/cmd_ingest.go b/cmd/rag/cmd_ingest.go index 67e20aa3..42ab76dc 100644 --- a/cmd/rag/cmd_ingest.go +++ b/cmd/rag/cmd_ingest.go @@ -6,7 +6,7 @@ import ( "forge.lthn.ai/core/go/pkg/cli" "forge.lthn.ai/core/go/pkg/i18n" - "forge.lthn.ai/core/go-ai/rag" + "forge.lthn.ai/core/go-rag" "github.com/spf13/cobra" ) diff --git a/cmd/rag/cmd_query.go b/cmd/rag/cmd_query.go index b058085f..e2679ff1 100644 --- a/cmd/rag/cmd_query.go +++ b/cmd/rag/cmd_query.go @@ -5,7 +5,7 @@ import ( "fmt" "forge.lthn.ai/core/go/pkg/i18n" - "forge.lthn.ai/core/go-ai/rag" + "forge.lthn.ai/core/go-rag" "github.com/spf13/cobra" ) diff --git a/go.mod b/go.mod index 1cdcbc28..a986b96a 100644 --- a/go.mod +++ b/go.mod @@ -4,11 +4,18 @@ go 1.25.5 require ( forge.lthn.ai/core/go v0.0.0 + forge.lthn.ai/core/go-agentic v0.0.0 forge.lthn.ai/core/go-ai v0.0.0 forge.lthn.ai/core/go-crypt v0.0.0 forge.lthn.ai/core/go-devops v0.0.0 + forge.lthn.ai/core/go-inference v0.0.0 + forge.lthn.ai/core/go-ml v0.0.0 + forge.lthn.ai/core/go-mlx v0.0.0 forge.lthn.ai/core/go-netops v0.0.0 + forge.lthn.ai/core/go-rag v0.0.0 forge.lthn.ai/core/go-scm v0.0.0 + forge.lthn.ai/core/go-store v0.0.0 + forge.lthn.ai/core/go-webview v0.0.0 ) require ( @@ -125,10 +132,17 @@ require ( ) replace ( - forge.lthn.ai/core/go => ../core + forge.lthn.ai/core/go => ../go + forge.lthn.ai/core/go-agentic => ../go-agentic forge.lthn.ai/core/go-ai => ../go-ai forge.lthn.ai/core/go-crypt => ../go-crypt forge.lthn.ai/core/go-devops => ../go-devops + forge.lthn.ai/core/go-inference => ../go-inference + forge.lthn.ai/core/go-ml => ../go-ml + forge.lthn.ai/core/go-mlx => ../go-mlx forge.lthn.ai/core/go-netops => ../go-netops + forge.lthn.ai/core/go-rag => ../go-rag forge.lthn.ai/core/go-scm => ../go-scm + forge.lthn.ai/core/go-store => ../go-store + forge.lthn.ai/core/go-webview => ../go-webview ) diff --git a/go.work b/go.work index d7bccbfa..5b53a4fd 100644 --- a/go.work +++ b/go.work @@ -2,10 +2,17 @@ go 1.25.5 use ( . - ../core + ../go + ../go-agentic ../go-ai ../go-crypt ../go-devops + ../go-inference + ../go-ml + ../go-mlx ../go-netops + ../go-rag ../go-scm + ../go-store + ../go-webview )