feat(cli): migrate imports to split repos + wire go-agentic registry
Virgil split go-ai into standalone modules (go-agentic, go-ml, go-mlx, go-rag). This migrates all CLI imports to the new module paths and fixes API mismatches from the split. Key changes: - go-ai/agentic → go-agentic (cmd/ai, cmd/dev) - go-ai/ml → go-ml (31 files in cmd/ml) - go-ai/rag → go-rag (3 files in cmd/rag) - go-ai/mlx → go-mlx (1 file) - Fix go.work path (../core → ../go) - Add all split repos to go.work and go.mod - Simplify daemon to goroutine-based MCP (remove missing supervisor) - Wire go-agentic SQLiteRegistry into dispatch watch (--agent-id flag) - Add `core ai agent fleet` command for local registry status - Fix rag collections API (PointCount, Status string) - Fix ml live/expand-status to use available go-ml API Co-Authored-By: Charon <charon@lethean.io>
This commit is contained in:
parent
41f786a222
commit
a43cc099cd
45 changed files with 307 additions and 120 deletions
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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{
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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{
|
||||
|
|
|
|||
|
|
@ -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{
|
||||
|
|
|
|||
|
|
@ -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{
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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{
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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{
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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{
|
||||
|
|
|
|||
|
|
@ -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{
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
)
|
||||
|
||||
|
|
|
|||
16
go.mod
16
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
|
||||
)
|
||||
|
|
|
|||
9
go.work
9
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
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue