fix(ax): continue AX naming cleanup

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-03-30 21:11:06 +00:00
parent 83703e1d99
commit f11d1d47a1
23 changed files with 633 additions and 633 deletions

View file

@ -28,7 +28,7 @@ func main() {
// core.Println(c.App().Name) // "core-agent"
// core.Println(c.App().Version) // "dev" or linked version
func newCoreAgent() *core.Core {
c := core.New(
coreApp := core.New(
core.WithOptions(core.NewOptions(core.Option{Key: "name", Value: "core-agent"})),
core.WithService(agentic.ProcessRegister),
core.WithService(agentic.Register),
@ -37,15 +37,15 @@ func newCoreAgent() *core.Core {
core.WithService(brain.Register),
core.WithService(registerMCPService),
)
c.App().Version = appVersion()
coreApp.App().Version = appVersion()
c.Cli().SetBanner(func(_ *core.Cli) string {
return core.Concat("core-agent ", c.App().Version, " — agentic orchestration for the Core ecosystem")
coreApp.Cli().SetBanner(func(_ *core.Cli) string {
return core.Concat("core-agent ", coreApp.App().Version, " — agentic orchestration for the Core ecosystem")
})
registerAppCommands(c)
registerAppCommands(coreApp)
return c
return coreApp
}
// appVersion resolves the build version injected at link time.
@ -69,19 +69,19 @@ func runCoreAgent() error {
// runApp starts services, runs the CLI with explicit args, then shuts down.
//
// err := runApp(c, []string{"version"})
func runApp(c *core.Core, cliArgs []string) error {
if c == nil {
func runApp(coreApp *core.Core, cliArgs []string) error {
if coreApp == nil {
return core.E("main.runApp", "core is required", nil)
}
defer c.ServiceShutdown(context.Background())
defer coreApp.ServiceShutdown(context.Background())
result := c.ServiceStartup(c.Context(), nil)
result := coreApp.ServiceStartup(coreApp.Context(), nil)
if !result.OK {
return resultError("main.runApp", "startup failed", result)
}
if cli := c.Cli(); cli != nil {
if cli := coreApp.Cli(); cli != nil {
result = cli.Run(cliArgs...)
if !result.OK {
return resultError("main.runApp", "cli failed", result)

View file

@ -4,7 +4,7 @@
// Each handler adapts (ctx, Options) → Result to call the existing MCP tool method.
// Registered during OnStartup — the Action registry IS the capability map.
//
// c.Action("agentic.dispatch").Run(ctx, opts)
// c.Action("agentic.dispatch").Run(ctx, options)
// c.Actions() // all registered capabilities
package agentic
@ -21,16 +21,16 @@ import (
// handleDispatch dispatches a subagent to work on a repo task.
//
// r := c.Action("agentic.dispatch").Run(ctx, core.NewOptions(
// result := c.Action("agentic.dispatch").Run(ctx, core.NewOptions(
// core.Option{Key: "repo", Value: "go-io"},
// core.Option{Key: "task", Value: "Fix tests"},
// ))
func (s *PrepSubsystem) handleDispatch(ctx context.Context, opts core.Options) core.Result {
func (s *PrepSubsystem) handleDispatch(ctx context.Context, options core.Options) core.Result {
input := DispatchInput{
Repo: opts.String("repo"),
Task: opts.String("task"),
Agent: opts.String("agent"),
Issue: opts.Int("issue"),
Repo: options.String("repo"),
Task: options.String("task"),
Agent: options.String("agent"),
Issue: options.Int("issue"),
}
_, out, err := s.dispatch(ctx, nil, input)
if err != nil {
@ -41,15 +41,15 @@ func (s *PrepSubsystem) handleDispatch(ctx context.Context, opts core.Options) c
// handlePrep prepares a workspace without dispatching an agent.
//
// r := c.Action("agentic.prep").Run(ctx, core.NewOptions(
// result := c.Action("agentic.prep").Run(ctx, core.NewOptions(
// core.Option{Key: "repo", Value: "go-io"},
// core.Option{Key: "issue", Value: 42},
// ))
func (s *PrepSubsystem) handlePrep(ctx context.Context, opts core.Options) core.Result {
func (s *PrepSubsystem) handlePrep(ctx context.Context, options core.Options) core.Result {
input := PrepInput{
Repo: opts.String("repo"),
Org: opts.String("org"),
Issue: opts.Int("issue"),
Repo: options.String("repo"),
Org: options.String("org"),
Issue: options.Int("issue"),
}
_, out, err := s.prepWorkspace(ctx, nil, input)
if err != nil {
@ -60,12 +60,12 @@ func (s *PrepSubsystem) handlePrep(ctx context.Context, opts core.Options) core.
// handleStatus lists workspace statuses.
//
// r := c.Action("agentic.status").Run(ctx, core.NewOptions())
func (s *PrepSubsystem) handleStatus(ctx context.Context, opts core.Options) core.Result {
// result := c.Action("agentic.status").Run(ctx, core.NewOptions())
func (s *PrepSubsystem) handleStatus(ctx context.Context, options core.Options) core.Result {
input := StatusInput{
Workspace: opts.String("workspace"),
Limit: opts.Int("limit"),
Status: opts.String("status"),
Workspace: options.String("workspace"),
Limit: options.Int("limit"),
Status: options.String("status"),
}
_, out, err := s.status(ctx, nil, input)
if err != nil {
@ -76,13 +76,13 @@ func (s *PrepSubsystem) handleStatus(ctx context.Context, opts core.Options) cor
// handleResume resumes a blocked workspace.
//
// r := c.Action("agentic.resume").Run(ctx, core.NewOptions(
// result := c.Action("agentic.resume").Run(ctx, core.NewOptions(
// core.Option{Key: "workspace", Value: "core/go-io/task-5"},
// ))
func (s *PrepSubsystem) handleResume(ctx context.Context, opts core.Options) core.Result {
func (s *PrepSubsystem) handleResume(ctx context.Context, options core.Options) core.Result {
input := ResumeInput{
Workspace: opts.String("workspace"),
Answer: opts.String("answer"),
Workspace: options.String("workspace"),
Answer: options.String("answer"),
}
_, out, err := s.resume(ctx, nil, input)
if err != nil {
@ -93,11 +93,11 @@ func (s *PrepSubsystem) handleResume(ctx context.Context, opts core.Options) cor
// handleScan scans forge repos for actionable issues.
//
// r := c.Action("agentic.scan").Run(ctx, core.NewOptions())
func (s *PrepSubsystem) handleScan(ctx context.Context, opts core.Options) core.Result {
// result := c.Action("agentic.scan").Run(ctx, core.NewOptions())
func (s *PrepSubsystem) handleScan(ctx context.Context, options core.Options) core.Result {
input := ScanInput{
Org: opts.String("org"),
Limit: opts.Int("limit"),
Org: options.String("org"),
Limit: options.Int("limit"),
}
_, out, err := s.scan(ctx, nil, input)
if err != nil {
@ -108,15 +108,15 @@ func (s *PrepSubsystem) handleScan(ctx context.Context, opts core.Options) core.
// handleWatch watches a workspace for completion.
//
// r := c.Action("agentic.watch").Run(ctx, core.NewOptions(
// result := c.Action("agentic.watch").Run(ctx, core.NewOptions(
// core.Option{Key: "workspace", Value: "core/go-io/task-5"},
// ))
func (s *PrepSubsystem) handleWatch(ctx context.Context, opts core.Options) core.Result {
func (s *PrepSubsystem) handleWatch(ctx context.Context, options core.Options) core.Result {
input := WatchInput{
PollInterval: opts.Int("poll_interval"),
Timeout: opts.Int("timeout"),
PollInterval: options.Int("poll_interval"),
Timeout: options.Int("timeout"),
}
if workspace := opts.String("workspace"); workspace != "" {
if workspace := options.String("workspace"); workspace != "" {
input.Workspaces = []string{workspace}
}
_, out, err := s.watch(ctx, nil, input)
@ -128,83 +128,83 @@ func (s *PrepSubsystem) handleWatch(ctx context.Context, opts core.Options) core
// handlePrompt reads an embedded prompt by slug.
//
// r := c.Action("agentic.prompt").Run(ctx, core.NewOptions(
// result := c.Action("agentic.prompt").Run(ctx, core.NewOptions(
// core.Option{Key: "slug", Value: "coding"},
// ))
func (s *PrepSubsystem) handlePrompt(_ context.Context, opts core.Options) core.Result {
return lib.Prompt(opts.String("slug"))
func (s *PrepSubsystem) handlePrompt(_ context.Context, options core.Options) core.Result {
return lib.Prompt(options.String("slug"))
}
// handleTask reads an embedded task plan by slug.
//
// r := c.Action("agentic.task").Run(ctx, core.NewOptions(
// result := c.Action("agentic.task").Run(ctx, core.NewOptions(
// core.Option{Key: "slug", Value: "bug-fix"},
// ))
func (s *PrepSubsystem) handleTask(_ context.Context, opts core.Options) core.Result {
return lib.Task(opts.String("slug"))
func (s *PrepSubsystem) handleTask(_ context.Context, options core.Options) core.Result {
return lib.Task(options.String("slug"))
}
// handleFlow reads an embedded flow by slug.
//
// r := c.Action("agentic.flow").Run(ctx, core.NewOptions(
// result := c.Action("agentic.flow").Run(ctx, core.NewOptions(
// core.Option{Key: "slug", Value: "go"},
// ))
func (s *PrepSubsystem) handleFlow(_ context.Context, opts core.Options) core.Result {
return lib.Flow(opts.String("slug"))
func (s *PrepSubsystem) handleFlow(_ context.Context, options core.Options) core.Result {
return lib.Flow(options.String("slug"))
}
// handlePersona reads an embedded persona by path.
//
// r := c.Action("agentic.persona").Run(ctx, core.NewOptions(
// result := c.Action("agentic.persona").Run(ctx, core.NewOptions(
// core.Option{Key: "path", Value: "code/backend-architect"},
// ))
func (s *PrepSubsystem) handlePersona(_ context.Context, opts core.Options) core.Result {
return lib.Persona(opts.String("path"))
func (s *PrepSubsystem) handlePersona(_ context.Context, options core.Options) core.Result {
return lib.Persona(options.String("path"))
}
// --- Pipeline ---
// handleComplete runs the named completion task.
//
// r := c.Action("agentic.complete").Run(ctx, core.NewOptions(
// result := c.Action("agentic.complete").Run(ctx, core.NewOptions(
// core.Option{Key: "workspace", Value: "/srv/.core/workspace/core/go-io/task-42"},
// ))
func (s *PrepSubsystem) handleComplete(ctx context.Context, opts core.Options) core.Result {
return s.Core().Task("agent.completion").Run(ctx, s.Core(), opts)
func (s *PrepSubsystem) handleComplete(ctx context.Context, options core.Options) core.Result {
return s.Core().Task("agent.completion").Run(ctx, s.Core(), options)
}
// handleQA runs build+test on a completed workspace.
//
// r := c.Action("agentic.qa").Run(ctx, core.NewOptions(
// result := c.Action("agentic.qa").Run(ctx, core.NewOptions(
// core.Option{Key: "workspace", Value: "/path/to/workspace"},
// ))
func (s *PrepSubsystem) handleQA(ctx context.Context, opts core.Options) core.Result {
func (s *PrepSubsystem) handleQA(ctx context.Context, options core.Options) core.Result {
// Feature flag gate — skip QA if disabled
if s.ServiceRuntime != nil && !s.Config().Enabled("auto-qa") {
return core.Result{Value: true, OK: true}
}
wsDir := opts.String("workspace")
wsDir := options.String("workspace")
if wsDir == "" {
return core.Result{Value: core.E("agentic.qa", "workspace is required", nil), OK: false}
}
passed := s.runQA(wsDir)
if !passed {
if result := ReadStatusResult(wsDir); result.OK {
st, ok := workspaceStatusValue(result)
workspaceStatus, ok := workspaceStatusValue(result)
if ok {
st.Status = "failed"
st.Question = "QA check failed — build or tests did not pass"
writeStatusResult(wsDir, st)
workspaceStatus.Status = "failed"
workspaceStatus.Question = "QA check failed — build or tests did not pass"
writeStatusResult(wsDir, workspaceStatus)
}
}
}
// Emit QA result for observability (monitor picks this up)
if s.ServiceRuntime != nil {
result := ReadStatusResult(wsDir)
st, ok := workspaceStatusValue(result)
workspaceStatus, ok := workspaceStatusValue(result)
repo := ""
if ok {
repo = st.Repo
repo = workspaceStatus.Repo
}
s.Core().ACTION(messages.QAResult{
Workspace: WorkspaceName(wsDir),
@ -217,14 +217,14 @@ func (s *PrepSubsystem) handleQA(ctx context.Context, opts core.Options) core.Re
// handleAutoPR creates a PR for a completed workspace.
//
// r := c.Action("agentic.auto-pr").Run(ctx, core.NewOptions(
// result := c.Action("agentic.auto-pr").Run(ctx, core.NewOptions(
// core.Option{Key: "workspace", Value: "/path/to/workspace"},
// ))
func (s *PrepSubsystem) handleAutoPR(ctx context.Context, opts core.Options) core.Result {
func (s *PrepSubsystem) handleAutoPR(ctx context.Context, options core.Options) core.Result {
if s.ServiceRuntime != nil && !s.Config().Enabled("auto-pr") {
return core.Result{OK: true}
}
wsDir := opts.String("workspace")
wsDir := options.String("workspace")
if wsDir == "" {
return core.Result{Value: core.E("agentic.auto-pr", "workspace is required", nil), OK: false}
}
@ -233,13 +233,13 @@ func (s *PrepSubsystem) handleAutoPR(ctx context.Context, opts core.Options) cor
// Emit PRCreated for observability
if s.ServiceRuntime != nil {
result := ReadStatusResult(wsDir)
st, ok := workspaceStatusValue(result)
if ok && st.PRURL != "" {
workspaceStatus, ok := workspaceStatusValue(result)
if ok && workspaceStatus.PRURL != "" {
s.Core().ACTION(messages.PRCreated{
Repo: st.Repo,
Branch: st.Branch,
PRURL: st.PRURL,
PRNum: extractPRNumber(st.PRURL),
Repo: workspaceStatus.Repo,
Branch: workspaceStatus.Branch,
PRURL: workspaceStatus.PRURL,
PRNum: extractPRNumber(workspaceStatus.PRURL),
})
}
}
@ -248,14 +248,14 @@ func (s *PrepSubsystem) handleAutoPR(ctx context.Context, opts core.Options) cor
// handleVerify verifies and auto-merges a PR.
//
// r := c.Action("agentic.verify").Run(ctx, core.NewOptions(
// result := c.Action("agentic.verify").Run(ctx, core.NewOptions(
// core.Option{Key: "workspace", Value: "/path/to/workspace"},
// ))
func (s *PrepSubsystem) handleVerify(ctx context.Context, opts core.Options) core.Result {
func (s *PrepSubsystem) handleVerify(ctx context.Context, options core.Options) core.Result {
if s.ServiceRuntime != nil && !s.Config().Enabled("auto-merge") {
return core.Result{OK: true}
}
wsDir := opts.String("workspace")
wsDir := options.String("workspace")
if wsDir == "" {
return core.Result{Value: core.E("agentic.verify", "workspace is required", nil), OK: false}
}
@ -264,20 +264,20 @@ func (s *PrepSubsystem) handleVerify(ctx context.Context, opts core.Options) cor
// Emit merge/review events for observability
if s.ServiceRuntime != nil {
result := ReadStatusResult(wsDir)
st, ok := workspaceStatusValue(result)
workspaceStatus, ok := workspaceStatusValue(result)
if ok {
if st.Status == "merged" {
if workspaceStatus.Status == "merged" {
s.Core().ACTION(messages.PRMerged{
Repo: st.Repo,
PRURL: st.PRURL,
PRNum: extractPRNumber(st.PRURL),
Repo: workspaceStatus.Repo,
PRURL: workspaceStatus.PRURL,
PRNum: extractPRNumber(workspaceStatus.PRURL),
})
} else if st.Question != "" {
} else if workspaceStatus.Question != "" {
s.Core().ACTION(messages.PRNeedsReview{
Repo: st.Repo,
PRURL: st.PRURL,
PRNum: extractPRNumber(st.PRURL),
Reason: st.Question,
Repo: workspaceStatus.Repo,
PRURL: workspaceStatus.PRURL,
PRNum: extractPRNumber(workspaceStatus.PRURL),
Reason: workspaceStatus.Question,
})
}
}
@ -287,11 +287,11 @@ func (s *PrepSubsystem) handleVerify(ctx context.Context, opts core.Options) cor
// handleIngest creates issues from agent findings.
//
// r := c.Action("agentic.ingest").Run(ctx, core.NewOptions(
// result := c.Action("agentic.ingest").Run(ctx, core.NewOptions(
// core.Option{Key: "workspace", Value: "/path/to/workspace"},
// ))
func (s *PrepSubsystem) handleIngest(ctx context.Context, opts core.Options) core.Result {
wsDir := opts.String("workspace")
func (s *PrepSubsystem) handleIngest(ctx context.Context, options core.Options) core.Result {
wsDir := options.String("workspace")
if wsDir == "" {
return core.Result{Value: core.E("agentic.ingest", "workspace is required", nil), OK: false}
}
@ -301,20 +301,20 @@ func (s *PrepSubsystem) handleIngest(ctx context.Context, opts core.Options) cor
// handlePoke drains the dispatch queue.
//
// r := c.Action("agentic.poke").Run(ctx, core.NewOptions())
func (s *PrepSubsystem) handlePoke(ctx context.Context, opts core.Options) core.Result {
// result := c.Action("agentic.poke").Run(ctx, core.NewOptions())
func (s *PrepSubsystem) handlePoke(ctx context.Context, _ core.Options) core.Result {
s.Poke()
return core.Result{OK: true}
}
// handleMirror mirrors agent branches to GitHub.
//
// r := c.Action("agentic.mirror").Run(ctx, core.NewOptions(
// result := c.Action("agentic.mirror").Run(ctx, core.NewOptions(
// core.Option{Key: "repo", Value: "go-io"},
// ))
func (s *PrepSubsystem) handleMirror(ctx context.Context, opts core.Options) core.Result {
func (s *PrepSubsystem) handleMirror(ctx context.Context, options core.Options) core.Result {
input := MirrorInput{
Repo: opts.String("repo"),
Repo: options.String("repo"),
}
_, out, err := s.mirror(ctx, nil, input)
if err != nil {
@ -327,74 +327,74 @@ func (s *PrepSubsystem) handleMirror(ctx context.Context, opts core.Options) cor
// handleIssueGet retrieves a forge issue.
//
// r := c.Action("agentic.issue.get").Run(ctx, core.NewOptions(
// result := c.Action("agentic.issue.get").Run(ctx, core.NewOptions(
// core.Option{Key: "repo", Value: "go-io"},
// core.Option{Key: "number", Value: "42"},
// ))
func (s *PrepSubsystem) handleIssueGet(ctx context.Context, opts core.Options) core.Result {
return s.cmdIssueGet(opts)
func (s *PrepSubsystem) handleIssueGet(ctx context.Context, options core.Options) core.Result {
return s.cmdIssueGet(options)
}
// handleIssueList lists forge issues.
//
// r := c.Action("agentic.issue.list").Run(ctx, core.NewOptions(
// result := c.Action("agentic.issue.list").Run(ctx, core.NewOptions(
// core.Option{Key: "_arg", Value: "go-io"},
// ))
func (s *PrepSubsystem) handleIssueList(ctx context.Context, opts core.Options) core.Result {
return s.cmdIssueList(opts)
func (s *PrepSubsystem) handleIssueList(ctx context.Context, options core.Options) core.Result {
return s.cmdIssueList(options)
}
// handleIssueCreate creates a forge issue.
//
// r := c.Action("agentic.issue.create").Run(ctx, core.NewOptions(
// result := c.Action("agentic.issue.create").Run(ctx, core.NewOptions(
// core.Option{Key: "_arg", Value: "go-io"},
// core.Option{Key: "title", Value: "Bug report"},
// ))
func (s *PrepSubsystem) handleIssueCreate(ctx context.Context, opts core.Options) core.Result {
return s.cmdIssueCreate(opts)
func (s *PrepSubsystem) handleIssueCreate(ctx context.Context, options core.Options) core.Result {
return s.cmdIssueCreate(options)
}
// handlePRGet retrieves a forge PR.
//
// r := c.Action("agentic.pr.get").Run(ctx, core.NewOptions(
// result := c.Action("agentic.pr.get").Run(ctx, core.NewOptions(
// core.Option{Key: "_arg", Value: "go-io"},
// core.Option{Key: "number", Value: "12"},
// ))
func (s *PrepSubsystem) handlePRGet(ctx context.Context, opts core.Options) core.Result {
return s.cmdPRGet(opts)
func (s *PrepSubsystem) handlePRGet(ctx context.Context, options core.Options) core.Result {
return s.cmdPRGet(options)
}
// handlePRList lists forge PRs.
//
// r := c.Action("agentic.pr.list").Run(ctx, core.NewOptions(
// result := c.Action("agentic.pr.list").Run(ctx, core.NewOptions(
// core.Option{Key: "_arg", Value: "go-io"},
// ))
func (s *PrepSubsystem) handlePRList(ctx context.Context, opts core.Options) core.Result {
return s.cmdPRList(opts)
func (s *PrepSubsystem) handlePRList(ctx context.Context, options core.Options) core.Result {
return s.cmdPRList(options)
}
// handlePRMerge merges a forge PR.
//
// r := c.Action("agentic.pr.merge").Run(ctx, core.NewOptions(
// result := c.Action("agentic.pr.merge").Run(ctx, core.NewOptions(
// core.Option{Key: "_arg", Value: "go-io"},
// core.Option{Key: "number", Value: "12"},
// ))
func (s *PrepSubsystem) handlePRMerge(ctx context.Context, opts core.Options) core.Result {
return s.cmdPRMerge(opts)
func (s *PrepSubsystem) handlePRMerge(ctx context.Context, options core.Options) core.Result {
return s.cmdPRMerge(options)
}
// --- Review ---
// handleReviewQueue runs CodeRabbit review on a workspace.
//
// r := c.Action("agentic.review-queue").Run(ctx, core.NewOptions(
// result := c.Action("agentic.review-queue").Run(ctx, core.NewOptions(
// core.Option{Key: "workspace", Value: "core/go-io/task-5"},
// ))
func (s *PrepSubsystem) handleReviewQueue(ctx context.Context, opts core.Options) core.Result {
func (s *PrepSubsystem) handleReviewQueue(ctx context.Context, options core.Options) core.Result {
input := ReviewQueueInput{
Limit: opts.Int("limit"),
Reviewer: opts.String("reviewer"),
DryRun: opts.Bool("dry_run"),
Limit: options.Int("limit"),
Reviewer: options.String("reviewer"),
DryRun: options.Bool("dry_run"),
}
_, out, err := s.reviewQueue(ctx, nil, input)
if err != nil {
@ -407,15 +407,15 @@ func (s *PrepSubsystem) handleReviewQueue(ctx context.Context, opts core.Options
// handleEpic creates an epic (multi-repo task breakdown).
//
// r := c.Action("agentic.epic").Run(ctx, core.NewOptions(
// result := c.Action("agentic.epic").Run(ctx, core.NewOptions(
// core.Option{Key: "task", Value: "Update all repos to v0.8.0"},
// ))
func (s *PrepSubsystem) handleEpic(ctx context.Context, opts core.Options) core.Result {
func (s *PrepSubsystem) handleEpic(ctx context.Context, options core.Options) core.Result {
input := EpicInput{
Repo: opts.String("repo"),
Org: opts.String("org"),
Title: opts.String("title"),
Body: opts.String("body"),
Repo: options.String("repo"),
Org: options.String("org"),
Title: options.String("title"),
Body: options.String("body"),
}
_, out, err := s.createEpic(ctx, nil, input)
if err != nil {
@ -426,20 +426,20 @@ func (s *PrepSubsystem) handleEpic(ctx context.Context, opts core.Options) core.
// handleWorkspaceQuery answers workspace state queries from Core QUERY calls.
//
// r := c.QUERY(agentic.WorkspaceQuery{Name: "core/go-io/task-42"})
// r := c.QUERY(agentic.WorkspaceQuery{Status: "blocked"})
func (s *PrepSubsystem) handleWorkspaceQuery(_ *core.Core, q core.Query) core.Result {
wq, ok := q.(WorkspaceQuery)
// result := c.QUERY(agentic.WorkspaceQuery{Name: "core/go-io/task-42"})
// result := c.QUERY(agentic.WorkspaceQuery{Status: "blocked"})
func (s *PrepSubsystem) handleWorkspaceQuery(_ *core.Core, query core.Query) core.Result {
workspaceQuery, ok := query.(WorkspaceQuery)
if !ok {
return core.Result{}
}
if wq.Name != "" {
return s.workspaces.Get(wq.Name)
if workspaceQuery.Name != "" {
return s.workspaces.Get(workspaceQuery.Name)
}
if wq.Status != "" {
if workspaceQuery.Status != "" {
var names []string
s.workspaces.Each(func(name string, st *WorkspaceStatus) {
if st.Status == wq.Status {
s.workspaces.Each(func(name string, workspaceStatus *WorkspaceStatus) {
if workspaceStatus.Status == workspaceQuery.Status {
names = append(names, name)
}
})

View file

@ -13,8 +13,8 @@ import (
// if the agent made any commits beyond the initial clone.
func (s *PrepSubsystem) autoCreatePR(wsDir string) {
result := ReadStatusResult(wsDir)
st, ok := workspaceStatusValue(result)
if !ok || st.Branch == "" || st.Repo == "" {
workspaceStatus, ok := workspaceStatusValue(result)
if !ok || workspaceStatus.Branch == "" || workspaceStatus.Repo == "" {
return
}
@ -25,78 +25,78 @@ func (s *PrepSubsystem) autoCreatePR(wsDir string) {
// PRs target dev — agents never merge directly to main
base := "dev"
r := process.RunIn(ctx, repoDir, "git", "log", "--oneline", core.Concat("origin/", base, "..HEAD"))
if !r.OK {
processResult := process.RunIn(ctx, repoDir, "git", "log", "--oneline", core.Concat("origin/", base, "..HEAD"))
if !processResult.OK {
return
}
out := core.Trim(r.Value.(string))
out := core.Trim(processResult.Value.(string))
if out == "" {
return
}
commitCount := len(core.Split(out, "\n"))
org := st.Org
org := workspaceStatus.Org
if org == "" {
org = "core"
}
// Push the branch to forge
forgeRemote := core.Sprintf("ssh://git@forge.lthn.ai:2223/%s/%s.git", org, st.Repo)
if !process.RunIn(ctx, repoDir, "git", "push", forgeRemote, st.Branch).OK {
forgeRemote := core.Sprintf("ssh://git@forge.lthn.ai:2223/%s/%s.git", org, workspaceStatus.Repo)
if !process.RunIn(ctx, repoDir, "git", "push", forgeRemote, workspaceStatus.Branch).OK {
if result := ReadStatusResult(wsDir); result.OK {
st2, ok := workspaceStatusValue(result)
workspaceStatusUpdate, ok := workspaceStatusValue(result)
if !ok {
return
}
st2.Question = "PR push failed"
writeStatusResult(wsDir, st2)
workspaceStatusUpdate.Question = "PR push failed"
writeStatusResult(wsDir, workspaceStatusUpdate)
}
return
}
// Create PR via Forge API
title := core.Sprintf("[agent/%s] %s", st.Agent, truncate(st.Task, 60))
body := s.buildAutoPRBody(st, commitCount)
title := core.Sprintf("[agent/%s] %s", workspaceStatus.Agent, truncate(workspaceStatus.Task, 60))
body := s.buildAutoPRBody(workspaceStatus, commitCount)
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
prURL, _, err := s.forgeCreatePR(ctx, org, st.Repo, st.Branch, base, title, body)
prURL, _, err := s.forgeCreatePR(ctx, org, workspaceStatus.Repo, workspaceStatus.Branch, base, title, body)
if err != nil {
if result := ReadStatusResult(wsDir); result.OK {
st2, ok := workspaceStatusValue(result)
workspaceStatusUpdate, ok := workspaceStatusValue(result)
if !ok {
return
}
st2.Question = core.Sprintf("PR creation failed: %v", err)
writeStatusResult(wsDir, st2)
workspaceStatusUpdate.Question = core.Sprintf("PR creation failed: %v", err)
writeStatusResult(wsDir, workspaceStatusUpdate)
}
return
}
// Update status with PR URL
if result := ReadStatusResult(wsDir); result.OK {
st2, ok := workspaceStatusValue(result)
workspaceStatusUpdate, ok := workspaceStatusValue(result)
if !ok {
return
}
st2.PRURL = prURL
writeStatusResult(wsDir, st2)
workspaceStatusUpdate.PRURL = prURL
writeStatusResult(wsDir, workspaceStatusUpdate)
}
}
func (s *PrepSubsystem) buildAutoPRBody(st *WorkspaceStatus, commits int) string {
func (s *PrepSubsystem) buildAutoPRBody(workspaceStatus *WorkspaceStatus, commits int) string {
b := core.NewBuilder()
b.WriteString("## Task\n\n")
b.WriteString(st.Task)
b.WriteString(workspaceStatus.Task)
b.WriteString("\n\n")
if st.Issue > 0 {
b.WriteString(core.Sprintf("Closes #%d\n\n", st.Issue))
if workspaceStatus.Issue > 0 {
b.WriteString(core.Sprintf("Closes #%d\n\n", workspaceStatus.Issue))
}
b.WriteString(core.Sprintf("**Agent:** %s\n", st.Agent))
b.WriteString(core.Sprintf("**Agent:** %s\n", workspaceStatus.Agent))
b.WriteString(core.Sprintf("**Commits:** %d\n", commits))
b.WriteString(core.Sprintf("**Branch:** `%s`\n", st.Branch))
b.WriteString(core.Sprintf("**Branch:** `%s`\n", workspaceStatus.Branch))
b.WriteString("\n---\n")
b.WriteString("Auto-created by core-agent dispatch system.\n")
b.WriteString("Co-Authored-By: Virgil <virgil@lethean.io>\n")

View file

@ -34,16 +34,16 @@ func (s *PrepSubsystem) commandContext() context.Context {
return context.Background()
}
func (s *PrepSubsystem) cmdRunTask(opts core.Options) core.Result {
return s.runTask(s.commandContext(), opts)
func (s *PrepSubsystem) cmdRunTask(options core.Options) core.Result {
return s.runTask(s.commandContext(), options)
}
func (s *PrepSubsystem) runTask(ctx context.Context, opts core.Options) core.Result {
repo := opts.String("repo")
agent := opts.String("agent")
task := opts.String("task")
issueStr := opts.String("issue")
org := opts.String("org")
func (s *PrepSubsystem) runTask(ctx context.Context, options core.Options) core.Result {
repo := options.String("repo")
agent := options.String("agent")
task := options.String("task")
issueStr := options.String("issue")
org := options.String("org")
if repo == "" || task == "" {
core.Print(nil, "usage: core-agent run task --repo=<repo> --task=\"...\" --agent=codex [--issue=N] [--org=core]")
@ -98,40 +98,40 @@ func (s *PrepSubsystem) cmdOrchestrator(_ core.Options) core.Result {
return core.Result{OK: true}
}
func (s *PrepSubsystem) cmdPrep(opts core.Options) core.Result {
repo := opts.String("_arg")
func (s *PrepSubsystem) cmdPrep(options core.Options) core.Result {
repo := options.String("_arg")
if repo == "" {
core.Print(nil, "usage: core-agent prep <repo> --issue=N|--pr=N|--branch=X --task=\"...\"")
return core.Result{Value: core.E("agentic.cmdPrep", "repo is required", nil), OK: false}
}
input := PrepInput{
prepInput := PrepInput{
Repo: repo,
Org: opts.String("org"),
Task: opts.String("task"),
Template: opts.String("template"),
Persona: opts.String("persona"),
DryRun: opts.Bool("dry-run"),
Org: options.String("org"),
Task: options.String("task"),
Template: options.String("template"),
Persona: options.String("persona"),
DryRun: options.Bool("dry-run"),
}
if v := opts.String("issue"); v != "" {
input.Issue = parseIntStr(v)
if value := options.String("issue"); value != "" {
prepInput.Issue = parseIntStr(value)
}
if v := opts.String("pr"); v != "" {
input.PR = parseIntStr(v)
if value := options.String("pr"); value != "" {
prepInput.PR = parseIntStr(value)
}
if v := opts.String("branch"); v != "" {
input.Branch = v
if value := options.String("branch"); value != "" {
prepInput.Branch = value
}
if v := opts.String("tag"); v != "" {
input.Tag = v
if value := options.String("tag"); value != "" {
prepInput.Tag = value
}
if input.Issue == 0 && input.PR == 0 && input.Branch == "" && input.Tag == "" {
input.Branch = "dev"
if prepInput.Issue == 0 && prepInput.PR == 0 && prepInput.Branch == "" && prepInput.Tag == "" {
prepInput.Branch = "dev"
}
_, out, err := s.TestPrepWorkspace(context.Background(), input)
_, out, err := s.TestPrepWorkspace(context.Background(), prepInput)
if err != nil {
core.Print(nil, "error: %v", err)
return core.Result{Value: err, OK: false}
@ -151,11 +151,11 @@ func (s *PrepSubsystem) cmdPrep(opts core.Options) core.Result {
return core.Result{OK: true}
}
func (s *PrepSubsystem) cmdStatus(opts core.Options) core.Result {
func (s *PrepSubsystem) cmdStatus(_ core.Options) core.Result {
wsRoot := WorkspaceRoot()
fsys := s.Core().Fs()
r := fsys.List(wsRoot)
if !r.OK {
listResult := fsys.List(wsRoot)
if !listResult.OK {
core.Print(nil, "no workspaces found at %s", wsRoot)
return core.Result{OK: true}
}
@ -172,33 +172,33 @@ func (s *PrepSubsystem) cmdStatus(opts core.Options) core.Result {
return core.Result{OK: true}
}
func (s *PrepSubsystem) cmdPrompt(opts core.Options) core.Result {
repo := opts.String("_arg")
func (s *PrepSubsystem) cmdPrompt(options core.Options) core.Result {
repo := options.String("_arg")
if repo == "" {
core.Print(nil, "usage: core-agent prompt <repo> --task=\"...\"")
return core.Result{Value: core.E("agentic.cmdPrompt", "repo is required", nil), OK: false}
}
org := opts.String("org")
org := options.String("org")
if org == "" {
org = "core"
}
task := opts.String("task")
task := options.String("task")
if task == "" {
task = "Review and report findings"
}
repoPath := core.JoinPath(HomeDir(), "Code", org, repo)
input := PrepInput{
prepInput := PrepInput{
Repo: repo,
Org: org,
Task: task,
Template: opts.String("template"),
Persona: opts.String("persona"),
Template: options.String("template"),
Persona: options.String("persona"),
}
prompt, memories, consumers := s.TestBuildPrompt(context.Background(), input, "dev", repoPath)
prompt, memories, consumers := s.TestBuildPrompt(context.Background(), prepInput, "dev", repoPath)
core.Print(nil, "memories: %d", memories)
core.Print(nil, "consumers: %d", consumers)
core.Print(nil, "")
@ -206,29 +206,29 @@ func (s *PrepSubsystem) cmdPrompt(opts core.Options) core.Result {
return core.Result{OK: true}
}
func (s *PrepSubsystem) cmdExtract(opts core.Options) core.Result {
tmpl := opts.String("_arg")
if tmpl == "" {
tmpl = "default"
func (s *PrepSubsystem) cmdExtract(options core.Options) core.Result {
templateName := options.String("_arg")
if templateName == "" {
templateName = "default"
}
target := opts.String("target")
target := options.String("target")
if target == "" {
target = core.JoinPath(WorkspaceRoot(), "test-extract")
}
data := &lib.WorkspaceData{
workspaceData := &lib.WorkspaceData{
Repo: "test-repo",
Branch: "dev",
Task: "test extraction",
Agent: "codex",
}
core.Print(nil, "extracting template %q to %s", tmpl, target)
if result := lib.ExtractWorkspace(tmpl, target, data); !result.OK {
core.Print(nil, "extracting template %q to %s", templateName, target)
if result := lib.ExtractWorkspace(templateName, target, workspaceData); !result.OK {
if err, ok := result.Value.(error); ok {
return core.Result{Value: core.E("agentic.cmdExtract", core.Concat("extract workspace template ", tmpl), err), OK: false}
return core.Result{Value: core.E("agentic.cmdExtract", core.Concat("extract workspace template ", templateName), err), OK: false}
}
return core.Result{Value: core.E("agentic.cmdExtract", core.Concat("extract workspace template ", tmpl), nil), OK: false}
return core.Result{Value: core.E("agentic.cmdExtract", core.Concat("extract workspace template ", templateName), nil), OK: false}
}
fsys := s.Core().Fs()

View file

@ -71,14 +71,14 @@ func pullRequestAuthor(pr pullRequestView) string {
return pr.User.Login
}
// parseForgeArgs extracts org and repo from opts.
func parseForgeArgs(opts core.Options) (org, repo string, num int64) {
org = opts.String("org")
// parseForgeArgs extracts org and repo from options.
func parseForgeArgs(options core.Options) (org, repo string, num int64) {
org = options.String("org")
if org == "" {
org = "core"
}
repo = opts.String("_arg")
if v := opts.String("number"); v != "" {
repo = options.String("_arg")
if v := options.String("number"); v != "" {
num, _ = strconv.ParseInt(v, 10, 64)
}
return
@ -100,9 +100,9 @@ func (s *PrepSubsystem) registerForgeCommands() {
c.Command("repo/list", core.Command{Description: "List Forge repos for an org", Action: s.cmdRepoList})
}
func (s *PrepSubsystem) cmdIssueGet(opts core.Options) core.Result {
func (s *PrepSubsystem) cmdIssueGet(options core.Options) core.Result {
ctx := context.Background()
org, repo, num := parseForgeArgs(opts)
org, repo, num := parseForgeArgs(options)
if repo == "" || num == 0 {
core.Print(nil, "usage: core-agent issue get <repo> --number=N [--org=core]")
return core.Result{Value: core.E("agentic.cmdIssueGet", "repo and number are required", nil), OK: false}
@ -123,9 +123,9 @@ func (s *PrepSubsystem) cmdIssueGet(opts core.Options) core.Result {
return core.Result{OK: true}
}
func (s *PrepSubsystem) cmdIssueList(opts core.Options) core.Result {
func (s *PrepSubsystem) cmdIssueList(options core.Options) core.Result {
ctx := context.Background()
org, repo, _ := parseForgeArgs(opts)
org, repo, _ := parseForgeArgs(options)
if repo == "" {
core.Print(nil, "usage: core-agent issue list <repo> [--org=core]")
return core.Result{Value: core.E("agentic.cmdIssueList", "repo is required", nil), OK: false}
@ -145,10 +145,10 @@ func (s *PrepSubsystem) cmdIssueList(opts core.Options) core.Result {
return core.Result{OK: true}
}
func (s *PrepSubsystem) cmdIssueComment(opts core.Options) core.Result {
func (s *PrepSubsystem) cmdIssueComment(options core.Options) core.Result {
ctx := context.Background()
org, repo, num := parseForgeArgs(opts)
body := opts.String("body")
org, repo, num := parseForgeArgs(options)
body := options.String("body")
if repo == "" || num == 0 || body == "" {
core.Print(nil, "usage: core-agent issue comment <repo> --number=N --body=\"text\" [--org=core]")
return core.Result{Value: core.E("agentic.cmdIssueComment", "repo, number, and body are required", nil), OK: false}
@ -162,15 +162,15 @@ func (s *PrepSubsystem) cmdIssueComment(opts core.Options) core.Result {
return core.Result{OK: true}
}
func (s *PrepSubsystem) cmdIssueCreate(opts core.Options) core.Result {
func (s *PrepSubsystem) cmdIssueCreate(options core.Options) core.Result {
ctx := context.Background()
org, repo, _ := parseForgeArgs(opts)
title := opts.String("title")
body := opts.String("body")
labels := opts.String("labels")
milestone := opts.String("milestone")
assignee := opts.String("assignee")
ref := opts.String("ref")
org, repo, _ := parseForgeArgs(options)
title := options.String("title")
body := options.String("body")
labels := options.String("labels")
milestone := options.String("milestone")
assignee := options.String("assignee")
ref := options.String("ref")
if repo == "" || title == "" {
core.Print(nil, "usage: core-agent issue create <repo> --title=\"...\" [--body=\"...\"] [--labels=\"agentic,bug\"] [--milestone=\"v0.2.0\"] [--assignee=virgil] [--ref=dev] [--org=core]")
return core.Result{Value: core.E("agentic.cmdIssueCreate", "repo and title are required", nil), OK: false}
@ -219,9 +219,9 @@ func (s *PrepSubsystem) cmdIssueCreate(opts core.Options) core.Result {
return core.Result{Value: issue.Index, OK: true}
}
func (s *PrepSubsystem) cmdPRGet(opts core.Options) core.Result {
func (s *PrepSubsystem) cmdPRGet(options core.Options) core.Result {
ctx := context.Background()
org, repo, num := parseForgeArgs(opts)
org, repo, num := parseForgeArgs(options)
if repo == "" || num == 0 {
core.Print(nil, "usage: core-agent pr get <repo> --number=N [--org=core]")
return core.Result{Value: core.E("agentic.cmdPRGet", "repo and number are required", nil), OK: false}
@ -245,9 +245,9 @@ func (s *PrepSubsystem) cmdPRGet(opts core.Options) core.Result {
return core.Result{OK: true}
}
func (s *PrepSubsystem) cmdPRList(opts core.Options) core.Result {
func (s *PrepSubsystem) cmdPRList(options core.Options) core.Result {
ctx := context.Background()
org, repo, _ := parseForgeArgs(opts)
org, repo, _ := parseForgeArgs(options)
if repo == "" {
core.Print(nil, "usage: core-agent pr list <repo> [--org=core]")
return core.Result{Value: core.E("agentic.cmdPRList", "repo is required", nil), OK: false}
@ -267,10 +267,10 @@ func (s *PrepSubsystem) cmdPRList(opts core.Options) core.Result {
return core.Result{OK: true}
}
func (s *PrepSubsystem) cmdPRMerge(opts core.Options) core.Result {
func (s *PrepSubsystem) cmdPRMerge(options core.Options) core.Result {
ctx := context.Background()
org, repo, num := parseForgeArgs(opts)
method := opts.String("method")
org, repo, num := parseForgeArgs(options)
method := options.String("method")
if method == "" {
method = "merge"
}
@ -286,30 +286,30 @@ func (s *PrepSubsystem) cmdPRMerge(opts core.Options) core.Result {
return core.Result{OK: true}
}
func (s *PrepSubsystem) cmdRepoGet(opts core.Options) core.Result {
func (s *PrepSubsystem) cmdRepoGet(options core.Options) core.Result {
ctx := context.Background()
org, repo, _ := parseForgeArgs(opts)
org, repo, _ := parseForgeArgs(options)
if repo == "" {
core.Print(nil, "usage: core-agent repo get <repo> [--org=core]")
return core.Result{Value: core.E("agentic.cmdRepoGet", "repo is required", nil), OK: false}
}
r, err := s.forge.Repos.Get(ctx, forge.Params{"owner": org, "repo": repo})
repositoryResult, err := s.forge.Repos.Get(ctx, forge.Params{"owner": org, "repo": repo})
if err != nil {
core.Print(nil, "error: %v", err)
return core.Result{Value: err, OK: false}
}
core.Print(nil, "%s/%s", r.Owner.UserName, r.Name)
core.Print(nil, " description: %s", r.Description)
core.Print(nil, " default: %s", r.DefaultBranch)
core.Print(nil, " private: %v", r.Private)
core.Print(nil, " archived: %v", r.Archived)
core.Print(nil, " url: %s", r.HTMLURL)
core.Print(nil, "%s/%s", repositoryResult.Owner.UserName, repositoryResult.Name)
core.Print(nil, " description: %s", repositoryResult.Description)
core.Print(nil, " default: %s", repositoryResult.DefaultBranch)
core.Print(nil, " private: %v", repositoryResult.Private)
core.Print(nil, " archived: %v", repositoryResult.Archived)
core.Print(nil, " url: %s", repositoryResult.HTMLURL)
return core.Result{OK: true}
}
func (s *PrepSubsystem) cmdRepoList(opts core.Options) core.Result {
func (s *PrepSubsystem) cmdRepoList(options core.Options) core.Result {
ctx := context.Background()
org := opts.String("org")
org := options.String("org")
if org == "" {
org = "core"
}
@ -318,12 +318,12 @@ func (s *PrepSubsystem) cmdRepoList(opts core.Options) core.Result {
core.Print(nil, "error: %v", err)
return core.Result{Value: err, OK: false}
}
for _, r := range repos {
for _, repository := range repos {
archived := ""
if r.Archived {
if repository.Archived {
archived = " (archived)"
}
core.Print(nil, " %-30s %s%s", r.Name, r.Description, archived)
core.Print(nil, " %-30s %s%s", repository.Name, repository.Description, archived)
}
core.Print(nil, "\n %d repos", len(repos))
return core.Result{OK: true}

View file

@ -25,11 +25,11 @@ func (s *PrepSubsystem) cmdWorkspaceList(_ core.Options) core.Result {
wsDir := core.PathDir(sf)
wsName := WorkspaceName(wsDir)
result := ReadStatusResult(wsDir)
st, ok := workspaceStatusValue(result)
workspaceStatus, ok := workspaceStatusValue(result)
if !ok {
continue
}
core.Print(nil, " %-8s %-8s %-10s %s", st.Status, st.Agent, st.Repo, wsName)
core.Print(nil, " %-8s %-8s %-10s %s", workspaceStatus.Status, workspaceStatus.Agent, workspaceStatus.Repo, wsName)
count++
}
if count == 0 {
@ -38,10 +38,10 @@ func (s *PrepSubsystem) cmdWorkspaceList(_ core.Options) core.Result {
return core.Result{OK: true}
}
func (s *PrepSubsystem) cmdWorkspaceClean(opts core.Options) core.Result {
func (s *PrepSubsystem) cmdWorkspaceClean(options core.Options) core.Result {
wsRoot := WorkspaceRoot()
fsys := s.Core().Fs()
filter := opts.String("_arg")
filter := options.String("_arg")
if filter == "" {
filter = "all"
}
@ -53,11 +53,11 @@ func (s *PrepSubsystem) cmdWorkspaceClean(opts core.Options) core.Result {
wsDir := core.PathDir(sf)
wsName := WorkspaceName(wsDir)
result := ReadStatusResult(wsDir)
st, ok := workspaceStatusValue(result)
workspaceStatus, ok := workspaceStatusValue(result)
if !ok {
continue
}
status := st.Status
status := workspaceStatus.Status
switch filter {
case "all":
@ -93,8 +93,8 @@ func (s *PrepSubsystem) cmdWorkspaceClean(opts core.Options) core.Result {
return core.Result{OK: true}
}
func (s *PrepSubsystem) cmdWorkspaceDispatch(opts core.Options) core.Result {
repo := opts.String("_arg")
func (s *PrepSubsystem) cmdWorkspaceDispatch(options core.Options) core.Result {
repo := options.String("_arg")
if repo == "" {
core.Print(nil, "usage: core-agent workspace dispatch <repo> --task=\"...\" --issue=N|--pr=N|--branch=X [--agent=codex]")
return core.Result{Value: core.E("agentic.cmdWorkspaceDispatch", "repo is required", nil), OK: false}
@ -104,13 +104,13 @@ func (s *PrepSubsystem) cmdWorkspaceDispatch(opts core.Options) core.Result {
// not gated by the frozen-queue entitlement.
input := DispatchInput{
Repo: repo,
Task: opts.String("task"),
Agent: opts.String("agent"),
Org: opts.String("org"),
Template: opts.String("template"),
Branch: opts.String("branch"),
Issue: parseIntStr(opts.String("issue")),
PR: parseIntStr(opts.String("pr")),
Task: options.String("task"),
Agent: options.String("agent"),
Org: options.String("org"),
Template: options.String("template"),
Branch: options.String("branch"),
Issue: parseIntStr(options.String("issue")),
PR: parseIntStr(options.String("pr")),
}
_, out, err := s.dispatch(context.Background(), nil, input)
if err != nil {

View file

@ -15,7 +15,7 @@ import (
// workspaceTracker is the interface runner.Service satisfies.
// Uses *WorkspaceStatus from agentic — runner imports agentic for the type.
type workspaceTracker interface {
TrackWorkspace(name string, st any)
TrackWorkspace(name string, status any)
}
// DispatchInput is the input for agentic_dispatch.
@ -60,16 +60,16 @@ func (s *PrepSubsystem) registerDispatchTool(server *mcp.Server) {
// agentCommand returns the command and args for a given agent type.
// Supports model variants: "gemini", "gemini:flash", "codex", "claude", "claude:haiku".
func agentCommand(agent, prompt string) (string, []string, error) {
r := agentCommandResult(agent, prompt)
if !r.OK {
err, _ := r.Value.(error)
commandResult := agentCommandResult(agent, prompt)
if !commandResult.OK {
err, _ := commandResult.Value.(error)
if err == nil {
err = core.E("agentCommand", "failed to resolve command", nil)
}
return "", nil, err
}
result, ok := r.Value.(agentCommandResultValue)
result, ok := commandResult.Value.(agentCommandResultValue)
if !ok {
return "", nil, core.E("agentCommand", "invalid command result", nil)
}
@ -242,8 +242,8 @@ func agentOutputFile(wsDir, agent string) string {
// Returns (status, question) — "completed", "blocked", or "failed".
func detectFinalStatus(repoDir string, exitCode int, procStatus string) (string, string) {
blockedPath := core.JoinPath(repoDir, "BLOCKED.md")
if r := fs.Read(blockedPath); r.OK && core.Trim(r.Value.(string)) != "" {
return "blocked", core.Trim(r.Value.(string))
if blockedResult := fs.Read(blockedPath); blockedResult.OK && core.Trim(blockedResult.Value.(string)) != "" {
return "blocked", core.Trim(blockedResult.Value.(string))
}
if exitCode != 0 || procStatus == "failed" || procStatus == "killed" {
question := ""
@ -283,15 +283,15 @@ func (s *PrepSubsystem) startIssueTracking(wsDir string) {
return
}
result := ReadStatusResult(wsDir)
st, ok := workspaceStatusValue(result)
if !ok || st.Issue == 0 {
workspaceStatus, ok := workspaceStatusValue(result)
if !ok || workspaceStatus.Issue == 0 {
return
}
org := st.Org
org := workspaceStatus.Org
if org == "" {
org = "core"
}
s.forge.Issues.StartStopwatch(context.Background(), org, st.Repo, int64(st.Issue))
s.forge.Issues.StartStopwatch(context.Background(), org, workspaceStatus.Repo, int64(workspaceStatus.Issue))
}
// stopIssueTracking stops a Forge stopwatch on the workspace's issue.
@ -300,25 +300,25 @@ func (s *PrepSubsystem) stopIssueTracking(wsDir string) {
return
}
result := ReadStatusResult(wsDir)
st, ok := workspaceStatusValue(result)
if !ok || st.Issue == 0 {
workspaceStatus, ok := workspaceStatusValue(result)
if !ok || workspaceStatus.Issue == 0 {
return
}
org := st.Org
org := workspaceStatus.Org
if org == "" {
org = "core"
}
s.forge.Issues.StopStopwatch(context.Background(), org, st.Repo, int64(st.Issue))
s.forge.Issues.StopStopwatch(context.Background(), org, workspaceStatus.Repo, int64(workspaceStatus.Issue))
}
// broadcastStart emits IPC + audit events for agent start.
func (s *PrepSubsystem) broadcastStart(agent, wsDir string) {
wsName := WorkspaceName(wsDir)
result := ReadStatusResult(wsDir)
st, ok := workspaceStatusValue(result)
workspaceStatus, ok := workspaceStatusValue(result)
repo := ""
if ok {
repo = st.Repo
repo = workspaceStatus.Repo
}
if s.ServiceRuntime != nil {
s.Core().ACTION(messages.AgentStarted{
@ -334,10 +334,10 @@ func (s *PrepSubsystem) broadcastComplete(agent, wsDir, finalStatus string) {
emitCompletionEvent(agent, wsName, finalStatus)
if s.ServiceRuntime != nil {
result := ReadStatusResult(wsDir)
st, ok := workspaceStatusValue(result)
workspaceStatus, ok := workspaceStatusValue(result)
repo := ""
if ok {
repo = st.Repo
repo = workspaceStatus.Repo
}
s.Core().ACTION(messages.AgentCompleted{
Agent: agent, Repo: repo,
@ -359,16 +359,16 @@ func (s *PrepSubsystem) onAgentComplete(agent, wsDir, outputFile string, exitCod
// Update workspace status (disk + registry)
result := ReadStatusResult(wsDir)
st, ok := workspaceStatusValue(result)
workspaceStatus, ok := workspaceStatusValue(result)
if ok {
st.Status = finalStatus
st.PID = 0
st.Question = question
writeStatusResult(wsDir, st)
s.TrackWorkspace(WorkspaceName(wsDir), st)
workspaceStatus.Status = finalStatus
workspaceStatus.PID = 0
workspaceStatus.Question = question
writeStatusResult(wsDir, workspaceStatus)
s.TrackWorkspace(WorkspaceName(wsDir), workspaceStatus)
// Rate-limit tracking
s.trackFailureRate(agent, finalStatus, st.StartedAt)
s.trackFailureRate(agent, finalStatus, workspaceStatus.StartedAt)
}
// Forge time tracking
@ -380,7 +380,7 @@ func (s *PrepSubsystem) onAgentComplete(agent, wsDir, outputFile string, exitCod
// Run completion pipeline via PerformAsync for successful agents.
// Gets ActionTaskStarted/Completed broadcasts + WaitGroup integration for graceful shutdown.
//
// c.PerformAsync("agentic.complete", opts) → runs agent.completion Task in background
// c.PerformAsync("agentic.complete", options) → runs agent.completion Task in background
if finalStatus == "completed" && s.ServiceRuntime != nil {
s.Core().PerformAsync("agentic.complete", core.NewOptions(
core.Option{Key: "workspace", Value: wsDir},
@ -568,13 +568,13 @@ func (s *PrepSubsystem) dispatch(ctx context.Context, req *mcp.CallToolRequest,
// Step 2: Ask runner service for permission (frozen + concurrency check).
// Runner owns the gate — agentic owns the spawn.
if s.ServiceRuntime != nil {
r := s.Core().Action("runner.dispatch").Run(ctx, core.NewOptions(
dispatchResult := s.Core().Action("runner.dispatch").Run(ctx, core.NewOptions(
core.Option{Key: "agent", Value: input.Agent},
core.Option{Key: "repo", Value: input.Repo},
))
if !r.OK {
if !dispatchResult.OK {
// Runner denied — queue it
st := &WorkspaceStatus{
workspaceStatus := &WorkspaceStatus{
Status: "queued",
Agent: input.Agent,
Repo: input.Repo,
@ -584,9 +584,9 @@ func (s *PrepSubsystem) dispatch(ctx context.Context, req *mcp.CallToolRequest,
StartedAt: time.Now(),
Runs: 0,
}
writeStatusResult(wsDir, st)
writeStatusResult(wsDir, workspaceStatus)
if runnerSvc, ok := core.ServiceFor[workspaceTracker](s.Core(), "runner"); ok {
runnerSvc.TrackWorkspace(WorkspaceName(wsDir), st)
runnerSvc.TrackWorkspace(WorkspaceName(wsDir), workspaceStatus)
}
return nil, DispatchOutput{
Success: true,
@ -604,7 +604,7 @@ func (s *PrepSubsystem) dispatch(ctx context.Context, req *mcp.CallToolRequest,
return nil, DispatchOutput{}, err
}
st := &WorkspaceStatus{
workspaceStatus := &WorkspaceStatus{
Status: "running",
Agent: input.Agent,
Repo: input.Repo,
@ -616,11 +616,11 @@ func (s *PrepSubsystem) dispatch(ctx context.Context, req *mcp.CallToolRequest,
StartedAt: time.Now(),
Runs: 1,
}
writeStatusResult(wsDir, st)
writeStatusResult(wsDir, workspaceStatus)
// Track in runner's registry (runner owns workspace state)
if s.ServiceRuntime != nil {
if runnerSvc, ok := core.ServiceFor[workspaceTracker](s.Core(), "runner"); ok {
runnerSvc.TrackWorkspace(WorkspaceName(wsDir), st)
runnerSvc.TrackWorkspace(WorkspaceName(wsDir), workspaceStatus)
}
}

View file

@ -39,15 +39,15 @@ func (s *PrepSubsystem) HandleIPCEvents(c *core.Core, msg core.Message) core.Res
}
// Update status with real PID
if result := ReadStatusResult(wsDir); result.OK {
st, ok := workspaceStatusValue(result)
workspaceStatus, ok := workspaceStatusValue(result)
if !ok {
break
}
st.PID = pid
st.ProcessID = processID
writeStatusResult(wsDir, st)
workspaceStatus.PID = pid
workspaceStatus.ProcessID = processID
writeStatusResult(wsDir, workspaceStatus)
if runnerSvc, ok := core.ServiceFor[workspaceTracker](c, "runner"); ok {
runnerSvc.TrackWorkspace(WorkspaceName(wsDir), st)
runnerSvc.TrackWorkspace(WorkspaceName(wsDir), workspaceStatus)
}
}
_ = outputFile
@ -59,8 +59,8 @@ func (s *PrepSubsystem) HandleIPCEvents(c *core.Core, msg core.Message) core.Res
// SpawnFromQueue spawns an agent in a pre-prepped workspace.
// Called by runner.Service via ServiceFor interface matching.
//
// r := prep.SpawnFromQueue("codex", prompt, wsDir)
// pid := r.Value.(int)
// spawnResult := prep.SpawnFromQueue("codex", prompt, wsDir)
// pid := spawnResult.Value.(int)
func (s *PrepSubsystem) SpawnFromQueue(agent, prompt, wsDir string) core.Result {
pid, _, _, err := s.spawnAgent(agent, prompt, wsDir)
if err != nil {
@ -88,12 +88,12 @@ func resolveWorkspace(name string) string {
func findWorkspaceByPR(repo, branch string) string {
for _, path := range WorkspaceStatusPaths() {
wsDir := core.PathDir(path)
result := ReadStatusResult(wsDir)
st, ok := workspaceStatusValue(result)
statusResult := ReadStatusResult(wsDir)
workspaceStatus, ok := workspaceStatusValue(statusResult)
if !ok {
continue
}
if st.Repo == repo && st.Branch == branch {
if workspaceStatus.Repo == repo && workspaceStatus.Branch == branch {
return wsDir
}
}

View file

@ -60,25 +60,25 @@ func (s *PrepSubsystem) createPR(ctx context.Context, _ *mcp.CallToolRequest, in
// Read workspace status for repo, branch, issue context
result := ReadStatusResult(wsDir)
st, ok := workspaceStatusValue(result)
workspaceStatus, ok := workspaceStatusValue(result)
if !ok {
err, _ := result.Value.(error)
return nil, CreatePROutput{}, core.E("createPR", "no status.json", err)
}
if st.Branch == "" {
if workspaceStatus.Branch == "" {
process := s.Core().Process()
r := process.RunIn(ctx, repoDir, "git", "rev-parse", "--abbrev-ref", "HEAD")
if !r.OK {
result := process.RunIn(ctx, repoDir, "git", "rev-parse", "--abbrev-ref", "HEAD")
if !result.OK {
return nil, CreatePROutput{}, core.E("createPR", "failed to detect branch", nil)
}
st.Branch = core.Trim(r.Value.(string))
if st.Branch == "" {
workspaceStatus.Branch = core.Trim(result.Value.(string))
if workspaceStatus.Branch == "" {
return nil, CreatePROutput{}, core.E("createPR", "failed to detect branch", nil)
}
}
org := st.Org
org := workspaceStatus.Org
if org == "" {
org = "core"
}
@ -90,48 +90,48 @@ func (s *PrepSubsystem) createPR(ctx context.Context, _ *mcp.CallToolRequest, in
// Build PR title
title := input.Title
if title == "" {
title = st.Task
title = workspaceStatus.Task
}
if title == "" {
title = core.Sprintf("Agent work on %s", st.Branch)
title = core.Sprintf("Agent work on %s", workspaceStatus.Branch)
}
// Build PR body
body := input.Body
if body == "" {
body = s.buildPRBody(st)
body = s.buildPRBody(workspaceStatus)
}
if input.DryRun {
return nil, CreatePROutput{
Success: true,
Title: title,
Branch: st.Branch,
Repo: st.Repo,
Branch: workspaceStatus.Branch,
Repo: workspaceStatus.Repo,
}, nil
}
// Push branch to Forge (origin is the local clone, not Forge)
forgeRemote := core.Sprintf("ssh://git@forge.lthn.ai:2223/%s/%s.git", org, st.Repo)
r := s.Core().Process().RunIn(ctx, repoDir, "git", "push", forgeRemote, st.Branch)
if !r.OK {
return nil, CreatePROutput{}, core.E("createPR", core.Concat("git push failed: ", r.Value.(string)), nil)
forgeRemote := core.Sprintf("ssh://git@forge.lthn.ai:2223/%s/%s.git", org, workspaceStatus.Repo)
pushResult := s.Core().Process().RunIn(ctx, repoDir, "git", "push", forgeRemote, workspaceStatus.Branch)
if !pushResult.OK {
return nil, CreatePROutput{}, core.E("createPR", core.Concat("git push failed: ", pushResult.Value.(string)), nil)
}
// Create PR via Forge API
prURL, prNum, err := s.forgeCreatePR(ctx, org, st.Repo, st.Branch, base, title, body)
prURL, prNum, err := s.forgeCreatePR(ctx, org, workspaceStatus.Repo, workspaceStatus.Branch, base, title, body)
if err != nil {
return nil, CreatePROutput{}, core.E("createPR", "failed to create PR", err)
}
// Update status with PR URL
st.PRURL = prURL
writeStatusResult(wsDir, st)
workspaceStatus.PRURL = prURL
writeStatusResult(wsDir, workspaceStatus)
// Comment on issue if tracked
if st.Issue > 0 {
if workspaceStatus.Issue > 0 {
comment := core.Sprintf("Pull request created: %s", prURL)
s.commentOnIssue(ctx, org, st.Repo, st.Issue, comment)
s.commentOnIssue(ctx, org, workspaceStatus.Repo, workspaceStatus.Issue, comment)
}
return nil, CreatePROutput{
@ -139,24 +139,24 @@ func (s *PrepSubsystem) createPR(ctx context.Context, _ *mcp.CallToolRequest, in
PRURL: prURL,
PRNum: prNum,
Title: title,
Branch: st.Branch,
Repo: st.Repo,
Branch: workspaceStatus.Branch,
Repo: workspaceStatus.Repo,
Pushed: true,
}, nil
}
func (s *PrepSubsystem) buildPRBody(st *WorkspaceStatus) string {
func (s *PrepSubsystem) buildPRBody(workspaceStatus *WorkspaceStatus) string {
b := core.NewBuilder()
b.WriteString("## Summary\n\n")
if st.Task != "" {
b.WriteString(st.Task)
if workspaceStatus.Task != "" {
b.WriteString(workspaceStatus.Task)
b.WriteString("\n\n")
}
if st.Issue > 0 {
b.WriteString(core.Sprintf("Closes #%d\n\n", st.Issue))
if workspaceStatus.Issue > 0 {
b.WriteString(core.Sprintf("Closes #%d\n\n", workspaceStatus.Issue))
}
b.WriteString(core.Sprintf("**Agent:** %s\n", st.Agent))
b.WriteString(core.Sprintf("**Runs:** %d\n", st.Runs))
b.WriteString(core.Sprintf("**Agent:** %s\n", workspaceStatus.Agent))
b.WriteString(core.Sprintf("**Runs:** %d\n", workspaceStatus.Runs))
b.WriteString("\n---\n*Created by agentic dispatch*\n")
return b.String()
}

View file

@ -37,8 +37,8 @@ func ProcessRegister(c *core.Core) core.Result {
if !ok {
return core.Result{Value: core.E("agentic.ProcessRegister", "unexpected process service type", nil), OK: false}
}
if r := c.RegisterService("process", service); !r.OK {
return r
if registerResult := c.RegisterService("process", service); !registerResult.OK {
return registerResult
}
handlers := &processActionHandlers{service: service}
@ -49,12 +49,12 @@ func ProcessRegister(c *core.Core) core.Result {
return core.Result{OK: true}
}
func (h *processActionHandlers) handleRun(ctx context.Context, opts core.Options) core.Result {
func (h *processActionHandlers) handleRun(ctx context.Context, options core.Options) core.Result {
output, err := h.service.RunWithOptions(ctx, process.RunOptions{
Command: opts.String("command"),
Args: optionStrings(opts, "args"),
Dir: opts.String("dir"),
Env: optionStrings(opts, "env"),
Command: options.String("command"),
Args: optionStrings(options, "args"),
Dir: options.String("dir"),
Env: optionStrings(options, "env"),
})
if err != nil {
return core.Result{Value: err, OK: false}
@ -62,13 +62,13 @@ func (h *processActionHandlers) handleRun(ctx context.Context, opts core.Options
return core.Result{Value: output, OK: true}
}
func (h *processActionHandlers) handleStart(ctx context.Context, opts core.Options) core.Result {
func (h *processActionHandlers) handleStart(ctx context.Context, options core.Options) core.Result {
proc, err := h.service.StartWithOptions(ctx, process.RunOptions{
Command: opts.String("command"),
Args: optionStrings(opts, "args"),
Dir: opts.String("dir"),
Env: optionStrings(opts, "env"),
Detach: opts.Bool("detach"),
Command: options.String("command"),
Args: optionStrings(options, "args"),
Dir: options.String("dir"),
Env: optionStrings(options, "env"),
Detach: options.Bool("detach"),
})
if err != nil {
return core.Result{Value: err, OK: false}
@ -76,8 +76,8 @@ func (h *processActionHandlers) handleStart(ctx context.Context, opts core.Optio
return core.Result{Value: proc, OK: true}
}
func (h *processActionHandlers) handleKill(_ context.Context, opts core.Options) core.Result {
id := opts.String("id")
func (h *processActionHandlers) handleKill(_ context.Context, options core.Options) core.Result {
id := options.String("id")
if id == "" {
return core.Result{Value: core.E("agentic.ProcessRegister", "process id is required", nil), OK: false}
}
@ -87,12 +87,12 @@ func (h *processActionHandlers) handleKill(_ context.Context, opts core.Options)
return core.Result{OK: true}
}
func optionStrings(opts core.Options, key string) []string {
r := opts.Get(key)
if !r.OK {
func optionStrings(options core.Options, key string) []string {
result := options.Get(key)
if !result.OK {
return nil
}
switch values := r.Value.(type) {
switch values := result.Value.(type) {
case []string:
return values
case []any:

View file

@ -87,12 +87,12 @@ func (s *PrepSubsystem) loadAgentsConfig() *AgentsConfig {
}
for _, path := range paths {
r := fs.Read(path)
if !r.OK {
readResult := fs.Read(path)
if !readResult.OK {
continue
}
var config AgentsConfig
if err := yaml.Unmarshal([]byte(r.Value.(string)), &config); err != nil {
if err := yaml.Unmarshal([]byte(readResult.Value.(string)), &config); err != nil {
continue
}
return &config
@ -169,8 +169,8 @@ func (s *PrepSubsystem) countRunningByAgent(agent string) int {
}
if s.workspaces != nil && s.workspaces.Len() > 0 {
count := 0
s.workspaces.Each(func(_ string, st *WorkspaceStatus) {
if st.Status == "running" && baseAgent(st.Agent) == agent && ProcessAlive(runtime, st.ProcessID, st.PID) {
s.workspaces.Each(func(_ string, workspaceStatus *WorkspaceStatus) {
if workspaceStatus.Status == "running" && baseAgent(workspaceStatus.Agent) == agent && ProcessAlive(runtime, workspaceStatus.ProcessID, workspaceStatus.PID) {
count++
}
})
@ -187,14 +187,14 @@ func (s *PrepSubsystem) countRunningByAgentDisk(runtime *core.Core, agent string
count := 0
for _, statusPath := range WorkspaceStatusPaths() {
result := ReadStatusResult(core.PathDir(statusPath))
st, ok := workspaceStatusValue(result)
if !ok || st.Status != "running" {
workspaceStatus, ok := workspaceStatusValue(result)
if !ok || workspaceStatus.Status != "running" {
continue
}
if baseAgent(st.Agent) != agent {
if baseAgent(workspaceStatus.Agent) != agent {
continue
}
if ProcessAlive(runtime, st.ProcessID, st.PID) {
if ProcessAlive(runtime, workspaceStatus.ProcessID, workspaceStatus.PID) {
count++
}
}
@ -212,8 +212,8 @@ func (s *PrepSubsystem) countRunningByModel(agent string) int {
}
if s.workspaces != nil && s.workspaces.Len() > 0 {
count := 0
s.workspaces.Each(func(_ string, st *WorkspaceStatus) {
if st.Status == "running" && st.Agent == agent && ProcessAlive(runtime, st.ProcessID, st.PID) {
s.workspaces.Each(func(_ string, workspaceStatus *WorkspaceStatus) {
if workspaceStatus.Status == "running" && workspaceStatus.Agent == agent && ProcessAlive(runtime, workspaceStatus.ProcessID, workspaceStatus.PID) {
count++
}
})
@ -230,14 +230,14 @@ func (s *PrepSubsystem) countRunningByModelDisk(runtime *core.Core, agent string
count := 0
for _, statusPath := range WorkspaceStatusPaths() {
result := ReadStatusResult(core.PathDir(statusPath))
st, ok := workspaceStatusValue(result)
if !ok || st.Status != "running" {
workspaceStatus, ok := workspaceStatusValue(result)
if !ok || workspaceStatus.Status != "running" {
continue
}
if st.Agent != agent {
if workspaceStatus.Agent != agent {
continue
}
if ProcessAlive(runtime, st.ProcessID, st.PID) {
if ProcessAlive(runtime, workspaceStatus.ProcessID, workspaceStatus.PID) {
count++
}
}
@ -255,9 +255,9 @@ func baseAgent(agent string) string {
func (s *PrepSubsystem) canDispatchAgent(agent string) bool {
var concurrency map[string]ConcurrencyLimit
if s.ServiceRuntime != nil {
r := s.Core().Config().Get("agents.concurrency")
if r.OK {
concurrency, _ = r.Value.(map[string]ConcurrencyLimit)
configurationResult := s.Core().Config().Get("agents.concurrency")
if configurationResult.OK {
concurrency, _ = configurationResult.Value.(map[string]ConcurrencyLimit)
}
}
if concurrency == nil {
@ -329,45 +329,45 @@ func (s *PrepSubsystem) drainOne() bool {
for _, statusPath := range WorkspaceStatusPaths() {
wsDir := core.PathDir(statusPath)
result := ReadStatusResult(wsDir)
st, ok := workspaceStatusValue(result)
if !ok || st.Status != "queued" {
workspaceStatus, ok := workspaceStatusValue(result)
if !ok || workspaceStatus.Status != "queued" {
continue
}
if !s.canDispatchAgent(st.Agent) {
if !s.canDispatchAgent(workspaceStatus.Agent) {
continue
}
// Skip if agent pool is in rate-limit backoff
pool := baseAgent(st.Agent)
pool := baseAgent(workspaceStatus.Agent)
if until, ok := s.backoff[pool]; ok && time.Now().Before(until) {
continue
}
// Apply rate delay before spawning
delay := s.delayForAgent(st.Agent)
delay := s.delayForAgent(workspaceStatus.Agent)
if delay > 0 {
time.Sleep(delay)
}
// Re-check concurrency after delay (another task may have started)
if !s.canDispatchAgent(st.Agent) {
if !s.canDispatchAgent(workspaceStatus.Agent) {
continue
}
prompt := core.Concat("TASK: ", st.Task, "\n\nResume from where you left off. Read CODEX.md for conventions. Commit when done.")
prompt := core.Concat("TASK: ", workspaceStatus.Task, "\n\nResume from where you left off. Read CODEX.md for conventions. Commit when done.")
pid, processID, _, err := s.spawnAgent(st.Agent, prompt, wsDir)
pid, processID, _, err := s.spawnAgent(workspaceStatus.Agent, prompt, wsDir)
if err != nil {
continue
}
st.Status = "running"
st.PID = pid
st.ProcessID = processID
st.Runs++
writeStatusResult(wsDir, st)
s.TrackWorkspace(WorkspaceName(wsDir), st)
workspaceStatus.Status = "running"
workspaceStatus.PID = pid
workspaceStatus.ProcessID = processID
workspaceStatus.Runs++
writeStatusResult(wsDir, workspaceStatus)
s.TrackWorkspace(WorkspaceName(wsDir), workspaceStatus)
return true
}

View file

@ -53,18 +53,18 @@ func (s *PrepSubsystem) resume(ctx context.Context, _ *mcp.CallToolRequest, inpu
// Read current status
result := ReadStatusResult(wsDir)
st, ok := workspaceStatusValue(result)
workspaceStatus, ok := workspaceStatusValue(result)
if !ok {
err, _ := result.Value.(error)
return nil, ResumeOutput{}, core.E("resume", "no status.json in workspace", err)
}
if st.Status != "blocked" && st.Status != "failed" && st.Status != "completed" {
return nil, ResumeOutput{}, core.E("resume", core.Concat("workspace is ", st.Status, ", not resumable (must be blocked, failed, or completed)"), nil)
if workspaceStatus.Status != "blocked" && workspaceStatus.Status != "failed" && workspaceStatus.Status != "completed" {
return nil, ResumeOutput{}, core.E("resume", core.Concat("workspace is ", workspaceStatus.Status, ", not resumable (must be blocked, failed, or completed)"), nil)
}
// Determine agent
agent := st.Agent
agent := workspaceStatus.Agent
if input.Agent != "" {
agent = input.Agent
}
@ -73,14 +73,14 @@ func (s *PrepSubsystem) resume(ctx context.Context, _ *mcp.CallToolRequest, inpu
if input.Answer != "" {
answerPath := workspaceAnswerPath(wsDir)
content := core.Sprintf("# Answer\n\n%s\n", input.Answer)
if r := fs.Write(answerPath, content); !r.OK {
err, _ := r.Value.(error)
if writeResult := fs.Write(answerPath, content); !writeResult.OK {
err, _ := writeResult.Value.(error)
return nil, ResumeOutput{}, core.E("resume", "failed to write ANSWER.md", err)
}
}
// Build resume prompt — inline the task and answer, no file references
prompt := core.Concat("You are resuming previous work.\n\nORIGINAL TASK:\n", st.Task)
prompt := core.Concat("You are resuming previous work.\n\nORIGINAL TASK:\n", workspaceStatus.Task)
if input.Answer != "" {
prompt = core.Concat(prompt, "\n\nANSWER TO YOUR QUESTION:\n", input.Answer)
}
@ -102,12 +102,12 @@ func (s *PrepSubsystem) resume(ctx context.Context, _ *mcp.CallToolRequest, inpu
}
// Update status
st.Status = "running"
st.PID = pid
st.ProcessID = processID
st.Runs++
st.Question = ""
writeStatusResult(wsDir, st)
workspaceStatus.Status = "running"
workspaceStatus.PID = pid
workspaceStatus.ProcessID = processID
workspaceStatus.Runs++
workspaceStatus.Question = ""
writeStatusResult(wsDir, workspaceStatus)
return nil, ResumeOutput{
Success: true,

View file

@ -18,18 +18,18 @@ import (
// agentic_dispatch repo=go-crypt template=verify persona=engineering/engineering-security-engineer
func (s *PrepSubsystem) autoVerifyAndMerge(wsDir string) {
result := ReadStatusResult(wsDir)
st, ok := workspaceStatusValue(result)
if !ok || st.PRURL == "" || st.Repo == "" {
workspaceStatus, ok := workspaceStatusValue(result)
if !ok || workspaceStatus.PRURL == "" || workspaceStatus.Repo == "" {
return
}
repoDir := WorkspaceRepoDir(wsDir)
org := st.Org
org := workspaceStatus.Org
if org == "" {
org = "core"
}
prNum := extractPRNumber(st.PRURL)
prNum := extractPRNumber(workspaceStatus.PRURL)
if prNum == 0 {
return
}
@ -47,7 +47,7 @@ func (s *PrepSubsystem) autoVerifyAndMerge(wsDir string) {
}
// Attempt 1: run tests and try to merge
mergeOutcome := s.attemptVerifyAndMerge(repoDir, org, st.Repo, st.Branch, prNum)
mergeOutcome := s.attemptVerifyAndMerge(repoDir, org, workspaceStatus.Repo, workspaceStatus.Branch, prNum)
if mergeOutcome == mergeSuccess {
markMerged()
return
@ -55,8 +55,8 @@ func (s *PrepSubsystem) autoVerifyAndMerge(wsDir string) {
// Attempt 2: rebase onto main and retry
if mergeOutcome == mergeConflict || mergeOutcome == testFailed {
if s.rebaseBranch(repoDir, st.Branch) {
if s.attemptVerifyAndMerge(repoDir, org, st.Repo, st.Branch, prNum) == mergeSuccess {
if s.rebaseBranch(repoDir, workspaceStatus.Branch) {
if s.attemptVerifyAndMerge(repoDir, org, workspaceStatus.Repo, workspaceStatus.Branch, prNum) == mergeSuccess {
markMerged()
return
}
@ -64,15 +64,15 @@ func (s *PrepSubsystem) autoVerifyAndMerge(wsDir string) {
}
// Both attempts failed — flag for human review
s.flagForReview(org, st.Repo, prNum, mergeOutcome)
s.flagForReview(org, workspaceStatus.Repo, prNum, mergeOutcome)
if result := ReadStatusResult(wsDir); result.OK {
st2, ok := workspaceStatusValue(result)
workspaceStatusUpdate, ok := workspaceStatusValue(result)
if !ok {
return
}
st2.Question = "Flagged for review — auto-merge failed after retry"
writeStatusResult(wsDir, st2)
workspaceStatusUpdate.Question = "Flagged for review — auto-merge failed after retry"
writeStatusResult(wsDir, workspaceStatusUpdate)
}
}
@ -99,7 +99,7 @@ func (s *PrepSubsystem) attemptVerifyAndMerge(repoDir, org, repo, branch string,
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if r := s.forgeMergePR(ctx, org, repo, prNum); !r.OK {
if mergeResult := s.forgeMergePR(ctx, org, repo, prNum); !mergeResult.OK {
comment := core.Sprintf("## Tests Passed — Merge Failed\n\n`%s` passed but merge failed", testResult.testCmd)
s.commentOnIssue(context.Background(), org, repo, prNum, comment)
return mergeConflict
@ -126,14 +126,14 @@ func (s *PrepSubsystem) rebaseBranch(repoDir, branch string) bool {
}
result := ReadStatusResult(core.PathDir(repoDir))
st, ok := workspaceStatusValue(result)
workspaceStatus, ok := workspaceStatusValue(result)
org := "core"
repo := ""
if ok {
if st.Org != "" {
org = st.Org
if workspaceStatus.Org != "" {
org = workspaceStatus.Org
}
repo = st.Repo
repo = workspaceStatus.Repo
}
forgeRemote := core.Sprintf("ssh://git@forge.lthn.ai:2223/%s/%s.git", org, repo)
return process.RunIn(ctx, repoDir, "git", "push", "--force-with-lease", forgeRemote, branch).OK
@ -176,8 +176,8 @@ func (s *PrepSubsystem) ensureLabel(ctx context.Context, org, repo, name, colour
// getLabelID fetches the ID of a label by name.
func (s *PrepSubsystem) getLabelID(ctx context.Context, org, repo, name string) int {
url := core.Sprintf("%s/api/v1/repos/%s/%s/labels", s.forgeURL, org, repo)
r := HTTPGet(ctx, url, s.forgeToken, "token")
if !r.OK {
getResult := HTTPGet(ctx, url, s.forgeToken, "token")
if !getResult.OK {
return 0
}
@ -185,7 +185,7 @@ func (s *PrepSubsystem) getLabelID(ctx context.Context, org, repo, name string)
ID int `json:"id"`
Name string `json:"name"`
}
core.JSONUnmarshalString(r.Value.(string), &labels)
core.JSONUnmarshalString(getResult.Value.(string), &labels)
for _, l := range labels {
if l.Name == name {
return l.ID
@ -202,12 +202,12 @@ type verifyResult struct {
testCmd string
}
func resultText(r core.Result) string {
if text, ok := r.Value.(string); ok {
func resultText(result core.Result) string {
if text, ok := result.Value.(string); ok {
return text
}
if r.Value != nil {
return core.Sprint(r.Value)
if result.Value != nil {
return core.Sprint(result.Value)
}
return ""
}
@ -229,52 +229,52 @@ func (s *PrepSubsystem) runVerification(repoDir string) verifyResult {
func (s *PrepSubsystem) runGoTests(repoDir string) verifyResult {
ctx := context.Background()
process := s.Core().Process()
r := process.RunWithEnv(ctx, repoDir, []string{"GOWORK=off"}, "go", "test", "./...", "-count=1", "-timeout", "120s")
out := resultText(r)
processResult := process.RunWithEnv(ctx, repoDir, []string{"GOWORK=off"}, "go", "test", "./...", "-count=1", "-timeout", "120s")
out := resultText(processResult)
exitCode := 0
if !r.OK {
if !processResult.OK {
exitCode = 1
}
return verifyResult{passed: r.OK, output: out, exitCode: exitCode, testCmd: "go test ./..."}
return verifyResult{passed: processResult.OK, output: out, exitCode: exitCode, testCmd: "go test ./..."}
}
func (s *PrepSubsystem) runPHPTests(repoDir string) verifyResult {
ctx := context.Background()
process := s.Core().Process()
r := process.RunIn(ctx, repoDir, "composer", "test", "--no-interaction")
if !r.OK {
composerResult := process.RunIn(ctx, repoDir, "composer", "test", "--no-interaction")
if !composerResult.OK {
// Try pest as fallback
r2 := process.RunIn(ctx, repoDir, "./vendor/bin/pest", "--no-interaction")
if !r2.OK {
fallbackResult := process.RunIn(ctx, repoDir, "./vendor/bin/pest", "--no-interaction")
if !fallbackResult.OK {
return verifyResult{passed: false, testCmd: "none", output: "No PHP test runner found (composer test and vendor/bin/pest both unavailable)", exitCode: 1}
}
return verifyResult{passed: true, output: resultText(r2), exitCode: 0, testCmd: "vendor/bin/pest"}
return verifyResult{passed: true, output: resultText(fallbackResult), exitCode: 0, testCmd: "vendor/bin/pest"}
}
return verifyResult{passed: true, output: resultText(r), exitCode: 0, testCmd: "composer test"}
return verifyResult{passed: true, output: resultText(composerResult), exitCode: 0, testCmd: "composer test"}
}
func (s *PrepSubsystem) runNodeTests(repoDir string) verifyResult {
r := fs.Read(core.JoinPath(repoDir, "package.json"))
if !r.OK {
packageResult := fs.Read(core.JoinPath(repoDir, "package.json"))
if !packageResult.OK {
return verifyResult{passed: true, testCmd: "none", output: "Could not read package.json"}
}
var pkg struct {
Scripts map[string]string `json:"scripts"`
}
if ur := core.JSONUnmarshalString(r.Value.(string), &pkg); !ur.OK || pkg.Scripts["test"] == "" {
if parseResult := core.JSONUnmarshalString(packageResult.Value.(string), &pkg); !parseResult.OK || pkg.Scripts["test"] == "" {
return verifyResult{passed: true, testCmd: "none", output: "No test script in package.json"}
}
ctx := context.Background()
process := s.Core().Process()
r = process.RunIn(ctx, repoDir, "npm", "test")
out := resultText(r)
testResult := process.RunIn(ctx, repoDir, "npm", "test")
out := resultText(testResult)
exitCode := 0
if !r.OK {
if !testResult.OK {
exitCode = 1
}
return verifyResult{passed: r.OK, output: out, exitCode: exitCode, testCmd: "npm test"}
return verifyResult{passed: testResult.OK, output: out, exitCode: exitCode, testCmd: "npm test"}
}
// forgeMergePR merges a PR via the Forge API.

View file

@ -18,12 +18,12 @@ import (
// fs provides unrestricted filesystem access for shared brain credentials.
//
// keyPath := core.JoinPath(home, ".claude", "brain.key")
// if r := fs.Read(keyPath); r.OK {
// apiKey = core.Trim(r.Value.(string))
// if readResult := fs.Read(keyPath); readResult.OK {
// apiKey = core.Trim(readResult.Value.(string))
// }
var fs = agentic.LocalFs()
func fieldString(values map[string]any, key string) string {
func stringField(values map[string]any, key string) string {
return core.Sprint(values[key])
}

View file

@ -38,8 +38,8 @@ func NewDirect() *DirectSubsystem {
if apiKey == "" {
keyPath = brainKeyPath(agentic.HomeDir())
if keyPath != "" {
if r := fs.Read(keyPath); r.OK {
apiKey = core.Trim(r.Value.(string))
if readResult := fs.Read(keyPath); readResult.OK {
apiKey = core.Trim(readResult.Value.(string))
if apiKey != "" {
core.Info("brain direct subsystem loaded API key from file", "path", keyPath)
}
@ -110,22 +110,22 @@ func (s *DirectSubsystem) apiCall(ctx context.Context, method, path string, body
if body != nil {
bodyStr = core.JSONMarshalString(body)
}
r := agentic.HTTPDo(ctx, method, requestURL, bodyStr, s.apiKey, "Bearer")
if !r.OK {
requestResult := agentic.HTTPDo(ctx, method, requestURL, bodyStr, s.apiKey, "Bearer")
if !requestResult.OK {
core.Error("brain API call failed", "method", method, "path", path)
if err, ok := r.Value.(error); ok {
if err, ok := requestResult.Value.(error); ok {
return core.Result{Value: core.E("brain.apiCall", "API call failed", err), OK: false}
}
if responseBody, ok := r.Value.(string); ok && responseBody != "" {
if responseBody, ok := requestResult.Value.(string); ok && responseBody != "" {
return core.Result{Value: core.E("brain.apiCall", core.Concat("API call failed: ", core.Trim(responseBody)), nil), OK: false}
}
return core.Result{Value: core.E("brain.apiCall", "API call failed", nil), OK: false}
}
var result map[string]any
if ur := core.JSONUnmarshalString(r.Value.(string), &result); !ur.OK {
if parseResult := core.JSONUnmarshalString(requestResult.Value.(string), &result); !parseResult.OK {
core.Error("brain API response parse failed", "method", method, "path", path)
err, _ := ur.Value.(error)
err, _ := parseResult.Value.(error)
return core.Result{Value: core.E("brain.apiCall", "parse response", err), OK: false}
}
@ -191,11 +191,11 @@ func (s *DirectSubsystem) recall(ctx context.Context, _ *mcp.CallToolRequest, in
for _, m := range mems {
if mm, ok := m.(map[string]any); ok {
mem := Memory{
Content: fieldString(mm, "content"),
Type: fieldString(mm, "type"),
Project: fieldString(mm, "project"),
AgentID: fieldString(mm, "agent_id"),
CreatedAt: fieldString(mm, "created_at"),
Content: stringField(mm, "content"),
Type: stringField(mm, "type"),
Project: stringField(mm, "project"),
AgentID: stringField(mm, "agent_id"),
CreatedAt: stringField(mm, "created_at"),
}
if id, ok := mm["id"].(string); ok {
mem.ID = id

View file

@ -165,12 +165,12 @@ func parseMessages(result map[string]any) []MessageItem {
mm, _ := m.(map[string]any)
messages = append(messages, MessageItem{
ID: toInt(mm["id"]),
From: fieldString(mm, "from"),
To: fieldString(mm, "to"),
Subject: fieldString(mm, "subject"),
Content: fieldString(mm, "content"),
From: stringField(mm, "from"),
To: stringField(mm, "to"),
Subject: stringField(mm, "subject"),
Content: stringField(mm, "content"),
Read: mm["read"] == true,
CreatedAt: fieldString(mm, "created_at"),
CreatedAt: stringField(mm, "created_at"),
})
}
return messages

View file

@ -62,26 +62,26 @@ func (m *Subsystem) harvestCompleted() string {
// harvestWorkspace checks a single workspace and pushes if ready.
func (m *Subsystem) harvestWorkspace(wsDir string) *harvestResult {
r := fs.Read(agentic.WorkspaceStatusPath(wsDir))
if !r.OK {
statusResult := fs.Read(agentic.WorkspaceStatusPath(wsDir))
if !statusResult.OK {
return nil
}
statusData, ok := resultString(r)
statusData, ok := resultString(statusResult)
if !ok {
return nil
}
var st struct {
var workspaceStatus struct {
Status string `json:"status"`
Repo string `json:"repo"`
Branch string `json:"branch"`
}
if r := core.JSONUnmarshalString(statusData, &st); !r.OK {
if parseResult := core.JSONUnmarshalString(statusData, &workspaceStatus); !parseResult.OK {
return nil
}
// Only harvest completed workspaces (not merged, running, etc.)
if st.Status != "completed" {
if workspaceStatus.Status != "completed" {
return nil
}
@ -91,7 +91,7 @@ func (m *Subsystem) harvestWorkspace(wsDir string) *harvestResult {
}
// Check if there are commits to push
branch := st.Branch
branch := workspaceStatus.Branch
if branch == "" {
branch = m.detectBranch(repoDir)
}
@ -109,7 +109,7 @@ func (m *Subsystem) harvestWorkspace(wsDir string) *harvestResult {
// Safety checks before pushing
if reason := m.checkSafety(repoDir); reason != "" {
updateStatus(wsDir, "rejected", reason)
return &harvestResult{repo: st.Repo, branch: branch, rejected: reason}
return &harvestResult{repo: workspaceStatus.Repo, branch: branch, rejected: reason}
}
// Count changed files
@ -120,16 +120,16 @@ func (m *Subsystem) harvestWorkspace(wsDir string) *harvestResult {
// explicit review (/review command), not silently in the background.
updateStatus(wsDir, "ready-for-review", "")
return &harvestResult{repo: st.Repo, branch: branch, files: files}
return &harvestResult{repo: workspaceStatus.Repo, branch: branch, files: files}
}
// gitOutput runs a git command and returns trimmed stdout via Core Process.
func (m *Subsystem) gitOutput(dir string, args ...string) string {
r := m.Core().Process().RunIn(context.Background(), dir, "git", args...)
if !r.OK {
processResult := m.Core().Process().RunIn(context.Background(), dir, "git", args...)
if !processResult.OK {
return ""
}
return core.Trim(r.Value.(string))
return core.Trim(processResult.Value.(string))
}
// gitOK runs a git command and returns true if it exits 0.
@ -235,9 +235,9 @@ func (m *Subsystem) countChangedFiles(srcDir string) int {
// pushBranch pushes the agent's branch to origin.
func (m *Subsystem) pushBranch(srcDir, branch string) error {
r := m.Core().Process().RunIn(context.Background(), srcDir, "git", "push", "origin", branch)
if !r.OK {
if err, ok := r.Value.(error); ok {
processResult := m.Core().Process().RunIn(context.Background(), srcDir, "git", "push", "origin", branch)
if !processResult.OK {
if err, ok := processResult.Value.(error); ok {
return core.E("harvest.pushBranch", "push failed", err)
}
return core.E("harvest.pushBranch", "push failed", nil)
@ -249,27 +249,27 @@ func (m *Subsystem) pushBranch(srcDir, branch string) error {
//
// updateStatus(wsDir, "ready-for-review", "")
func updateStatus(wsDir, status, question string) {
r := fs.Read(agentic.WorkspaceStatusPath(wsDir))
if !r.OK {
statusResult := fs.Read(agentic.WorkspaceStatusPath(wsDir))
if !statusResult.OK {
return
}
statusData, ok := resultString(r)
statusData, ok := resultString(statusResult)
if !ok {
return
}
var st map[string]any
if r := core.JSONUnmarshalString(statusData, &st); !r.OK {
var workspaceStatus map[string]any
if parseResult := core.JSONUnmarshalString(statusData, &workspaceStatus); !parseResult.OK {
return
}
st["status"] = status
workspaceStatus["status"] = status
if question != "" {
st["question"] = question
workspaceStatus["question"] = question
} else {
delete(st, "question") // clear stale question from previous state
delete(workspaceStatus, "question") // clear stale question from previous state
}
statusPath := agentic.WorkspaceStatusPath(wsDir)
if r := fs.WriteAtomic(statusPath, core.JSONMarshalString(st)); !r.OK {
if err, ok := r.Value.(error); ok {
if writeResult := fs.WriteAtomic(statusPath, core.JSONMarshalString(workspaceStatus)); !writeResult.OK {
if err, ok := writeResult.Value.(error); ok {
core.Warn("monitor.updateStatus: failed to write status", "path", statusPath, "reason", err)
return
}

View file

@ -19,8 +19,8 @@ import (
"github.com/modelcontextprotocol/go-sdk/mcp"
)
// r := fs.Read(core.JoinPath(wsRoot, name, "status.json"))
// if text, ok := resultString(r); ok { _ = core.JSONUnmarshalString(text, &st) }
// readResult := fs.Read(core.JoinPath(wsRoot, name, "status.json"))
// if text, ok := resultString(readResult); ok { _ = core.JSONUnmarshalString(text, &workspaceStatus) }
var fs = agentic.LocalFs()
type channelSender interface {
@ -42,16 +42,16 @@ func monitorBrainKey() string {
if k := core.Env("CORE_BRAIN_KEY"); k != "" {
return k
}
if r := fs.Read(brainKeyPath(agentic.HomeDir())); r.OK {
if value, ok := resultString(r); ok {
if readResult := fs.Read(brainKeyPath(agentic.HomeDir())); readResult.OK {
if value, ok := resultString(readResult); ok {
return core.Trim(value)
}
}
return ""
}
func resultString(r core.Result) (string, bool) {
value, ok := r.Value.(string)
func resultString(result core.Result) (string, bool) {
value, ok := result.Value.(string)
if !ok {
return "", false
}
@ -172,15 +172,15 @@ func (m *Subsystem) Start(ctx context.Context) {
}()
}
// r := service.OnStartup(context.Background())
// core.Println(r.OK)
// result := service.OnStartup(context.Background())
// core.Println(result.OK)
func (m *Subsystem) OnStartup(ctx context.Context) core.Result {
m.Start(ctx)
return core.Result{OK: true}
}
// r := service.OnShutdown(context.Background())
// core.Println(r.OK)
// result := service.OnShutdown(context.Background())
// core.Println(result.OK)
func (m *Subsystem) OnShutdown(ctx context.Context) core.Result {
_ = m.Shutdown(ctx)
return core.Result{OK: true}

View file

@ -69,16 +69,16 @@ func CoreRoot() string {
// result := ReadStatusResult("/srv/core/workspace/core/go-io/task-5")
// if result.OK { workspaceStatus := result.Value.(*WorkspaceStatus) }
func ReadStatusResult(wsDir string) core.Result {
result := agentic.ReadStatusResult(wsDir)
if !result.OK {
err, _ := result.Value.(error)
statusResult := agentic.ReadStatusResult(wsDir)
if !statusResult.OK {
err, _ := statusResult.Value.(error)
if err == nil {
return core.Result{Value: core.E("runner.ReadStatusResult", "failed to read status", nil), OK: false}
}
return core.Result{Value: core.E("runner.ReadStatusResult", "failed to read status", err), OK: false}
}
agenticStatus, ok := result.Value.(*agentic.WorkspaceStatus)
agenticStatus, ok := statusResult.Value.(*agentic.WorkspaceStatus)
if !ok || agenticStatus == nil {
return core.Result{Value: core.E("runner.ReadStatusResult", "invalid status payload", nil), OK: false}
}
@ -91,8 +91,8 @@ func ReadStatusResult(wsDir string) core.Result {
// WriteStatus writes `status.json` for one workspace directory.
//
// r := runner.WriteStatus("/srv/core/workspace/core/go-io/task-5", &runner.WorkspaceStatus{Status: "running", Agent: "codex"})
// core.Println(r.OK)
// result := runner.WriteStatus("/srv/core/workspace/core/go-io/task-5", &runner.WorkspaceStatus{Status: "running", Agent: "codex"})
// core.Println(result.OK)
func WriteStatus(wsDir string, status *WorkspaceStatus) core.Result {
if status == nil {
return core.Result{Value: core.E("runner.WriteStatus", "status is required", nil), OK: false}
@ -103,8 +103,8 @@ func WriteStatus(wsDir string, status *WorkspaceStatus) core.Result {
return core.Result{Value: core.E("runner.WriteStatus", "status conversion failed", nil), OK: false}
}
agenticStatus.UpdatedAt = time.Now()
if r := fs.WriteAtomic(agentic.WorkspaceStatusPath(wsDir), core.JSONMarshalString(agenticStatus)); !r.OK {
err, _ := r.Value.(error)
if writeResult := fs.WriteAtomic(agentic.WorkspaceStatusPath(wsDir), core.JSONMarshalString(agenticStatus)); !writeResult.OK {
err, _ := writeResult.Value.(error)
if err == nil {
return core.Result{Value: core.E("runner.WriteStatus", "failed to write status", nil), OK: false}
}

View file

@ -93,12 +93,12 @@ func (s *Service) loadAgentsConfig() *AgentsConfig {
core.JoinPath(CoreRoot(), "agents.yaml"),
}
for _, path := range paths {
r := fs.Read(path)
if !r.OK {
readResult := fs.Read(path)
if !readResult.OK {
continue
}
var config AgentsConfig
if err := yaml.Unmarshal([]byte(r.Value.(string)), &config); err != nil {
if err := yaml.Unmarshal([]byte(readResult.Value.(string)), &config); err != nil {
continue
}
return &config
@ -121,9 +121,9 @@ func (s *Service) loadAgentsConfig() *AgentsConfig {
func (s *Service) canDispatchAgent(agent string) (bool, string) {
var concurrency map[string]ConcurrencyLimit
if s.ServiceRuntime != nil {
r := s.Core().Config().Get("agents.concurrency")
if r.OK {
concurrency, _ = r.Value.(map[string]ConcurrencyLimit)
configurationResult := s.Core().Config().Get("agents.concurrency")
if configurationResult.OK {
concurrency, _ = configurationResult.Value.(map[string]ConcurrencyLimit)
}
}
if concurrency == nil {
@ -166,14 +166,14 @@ func (s *Service) countRunningByAgent(agent string) int {
runtime = s.Core()
}
count := 0
s.workspaces.Each(func(_ string, st *WorkspaceStatus) {
if st.Status != "running" || baseAgent(st.Agent) != agent {
s.workspaces.Each(func(_ string, workspaceStatus *WorkspaceStatus) {
if workspaceStatus.Status != "running" || baseAgent(workspaceStatus.Agent) != agent {
return
}
switch {
case st.PID < 0:
case workspaceStatus.PID < 0:
count++
case st.PID > 0 && agentic.ProcessAlive(runtime, "", st.PID):
case workspaceStatus.PID > 0 && agentic.ProcessAlive(runtime, "", workspaceStatus.PID):
count++
}
})
@ -189,14 +189,14 @@ func (s *Service) countRunningByModel(agent string) int {
runtime = s.Core()
}
count := 0
s.workspaces.Each(func(_ string, st *WorkspaceStatus) {
if st.Status != "running" || st.Agent != agent {
s.workspaces.Each(func(_ string, workspaceStatus *WorkspaceStatus) {
if workspaceStatus.Status != "running" || workspaceStatus.Agent != agent {
return
}
switch {
case st.PID < 0:
case workspaceStatus.PID < 0:
count++
case st.PID > 0 && agentic.ProcessAlive(runtime, "", st.PID):
case workspaceStatus.PID > 0 && agentic.ProcessAlive(runtime, "", workspaceStatus.PID):
count++
}
})
@ -221,30 +221,30 @@ func (s *Service) drainQueue() {
func (s *Service) drainOne() bool {
for _, statusPath := range agentic.WorkspaceStatusPaths() {
wsDir := core.PathDir(statusPath)
result := ReadStatusResult(wsDir)
if !result.OK {
statusResult := ReadStatusResult(wsDir)
if !statusResult.OK {
continue
}
st, ok := result.Value.(*WorkspaceStatus)
if !ok || st == nil || st.Status != "queued" {
workspaceStatus, ok := statusResult.Value.(*WorkspaceStatus)
if !ok || workspaceStatus == nil || workspaceStatus.Status != "queued" {
continue
}
if can, _ := s.canDispatchAgent(st.Agent); !can {
if can, _ := s.canDispatchAgent(workspaceStatus.Agent); !can {
continue
}
pool := baseAgent(st.Agent)
pool := baseAgent(workspaceStatus.Agent)
if until, ok := s.backoff[pool]; ok && time.Now().Before(until) {
continue
}
delay := s.delayForAgent(st.Agent)
delay := s.delayForAgent(workspaceStatus.Agent)
if delay > 0 {
time.Sleep(delay)
}
if can, _ := s.canDispatchAgent(st.Agent); !can {
if can, _ := s.canDispatchAgent(workspaceStatus.Agent); !can {
continue
}
@ -252,7 +252,7 @@ func (s *Service) drainOne() bool {
// agentic owns the actual process launch.
// Workspace name is relative path from workspace root (e.g. "core/go-ai/dev")
wsName := agentic.WorkspaceName(wsDir)
core.Info("drainOne: found queued workspace", "workspace", wsName, "agent", st.Agent)
core.Info("drainOne: found queued workspace", "workspace", wsName, "agent", workspaceStatus.Agent)
// Spawn directly — agentic is a Core service, use ServiceFor to get it
if s.ServiceRuntime == nil {
@ -261,13 +261,13 @@ func (s *Service) drainOne() bool {
type spawner interface {
SpawnFromQueue(agent, prompt, wsDir string) core.Result
}
prep, ok := core.ServiceFor[spawner](s.Core(), "agentic")
agenticService, ok := core.ServiceFor[spawner](s.Core(), "agentic")
if !ok {
core.Error("drainOne: agentic service not found")
continue
}
prompt := core.Concat("TASK: ", st.Task, "\n\nResume from where you left off. Read CODEX.md for conventions. Commit when done.")
spawnResult := prep.SpawnFromQueue(st.Agent, prompt, wsDir)
prompt := core.Concat("TASK: ", workspaceStatus.Task, "\n\nResume from where you left off. Read CODEX.md for conventions. Commit when done.")
spawnResult := agenticService.SpawnFromQueue(workspaceStatus.Agent, prompt, wsDir)
if !spawnResult.OK {
core.Error("drainOne: spawn failed", "workspace", wsName, "reason", core.Sprint(spawnResult.Value))
continue
@ -279,14 +279,14 @@ func (s *Service) drainOne() bool {
}
// Only mark running AFTER successful spawn
st.Status = "running"
st.PID = pid
st.Runs++
if r := WriteStatus(wsDir, st); !r.OK {
core.Error("drainOne: failed to write workspace status", "workspace", wsName, "reason", core.Sprint(r.Value))
workspaceStatus.Status = "running"
workspaceStatus.PID = pid
workspaceStatus.Runs++
if writeResult := WriteStatus(wsDir, workspaceStatus); !writeResult.OK {
core.Error("drainOne: failed to write workspace status", "workspace", wsName, "reason", core.Sprint(writeResult.Value))
continue
}
s.TrackWorkspace(wsName, st)
s.TrackWorkspace(wsName, workspaceStatus)
core.Info("drainOne: spawned", "pid", pid, "workspace", wsName)
return true

View file

@ -108,8 +108,8 @@ func (s *Service) OnStartup(ctx context.Context) core.Result {
// OnShutdown freezes the queue.
//
// r := service.OnShutdown(context.Background())
// if r.OK {
// result := service.OnShutdown(context.Background())
// if result.OK {
// core.Println(service.IsFrozen())
// }
func (s *Service) OnShutdown(_ context.Context) core.Result {
@ -129,9 +129,9 @@ func (s *Service) HandleIPCEvents(c *core.Core, msg core.Message) core.Result {
base := baseAgent(ev.Agent)
running := s.countRunningByAgent(base)
var limit int
r := c.Config().Get("agents.concurrency")
if r.OK {
if concurrency, ok := r.Value.(map[string]ConcurrencyLimit); ok {
configurationResult := c.Config().Get("agents.concurrency")
if configurationResult.OK {
if concurrency, ok := configurationResult.Value.(map[string]ConcurrencyLimit); ok {
if cl, has := concurrency[base]; has {
limit = cl.Total
}
@ -152,26 +152,26 @@ func (s *Service) HandleIPCEvents(c *core.Core, msg core.Message) core.Result {
case messages.AgentCompleted:
// Update workspace status in Registry so concurrency count drops
if ev.Workspace != "" {
if r := s.workspaces.Get(ev.Workspace); r.OK {
if st, ok := r.Value.(*WorkspaceStatus); ok && st.Status == "running" {
st.Status = ev.Status
st.PID = 0
if workspaceResult := s.workspaces.Get(ev.Workspace); workspaceResult.OK {
if workspaceStatus, ok := workspaceResult.Value.(*WorkspaceStatus); ok && workspaceStatus.Status == "running" {
workspaceStatus.Status = ev.Status
workspaceStatus.PID = 0
}
}
} else {
s.workspaces.Each(func(_ string, st *WorkspaceStatus) {
if st.Repo == ev.Repo && st.Status == "running" {
st.Status = ev.Status
st.PID = 0
s.workspaces.Each(func(_ string, workspaceStatus *WorkspaceStatus) {
if workspaceStatus.Repo == ev.Repo && workspaceStatus.Status == "running" {
workspaceStatus.Status = ev.Status
workspaceStatus.PID = 0
}
})
}
cBase := baseAgent(ev.Agent)
cRunning := s.countRunningByAgent(cBase)
var cLimit int
cr := c.Config().Get("agents.concurrency")
if cr.OK {
if concurrency, ok := cr.Value.(map[string]ConcurrencyLimit); ok {
completionResult := c.Config().Get("agents.concurrency")
if completionResult.OK {
if concurrency, ok := completionResult.Value.(map[string]ConcurrencyLimit); ok {
if cl, has := concurrency[cBase]; has {
cLimit = cl.Total
}
@ -222,54 +222,54 @@ func (s *Service) Poke() {
//
// s.TrackWorkspace("core/go-io/task-5", &WorkspaceStatus{Status: "running", Agent: "codex"})
// s.TrackWorkspace("core/go-io/task-5", &agentic.WorkspaceStatus{Status: "running", Agent: "codex"})
func (s *Service) TrackWorkspace(name string, st any) {
func (s *Service) TrackWorkspace(name string, status any) {
if s.workspaces == nil {
return
}
var ws *WorkspaceStatus
switch value := st.(type) {
var workspaceStatus *WorkspaceStatus
switch value := status.(type) {
case *WorkspaceStatus:
ws = value
workspaceStatus = value
case *agentic.WorkspaceStatus:
ws = runnerWorkspaceStatusFromAgentic(value)
workspaceStatus = runnerWorkspaceStatusFromAgentic(value)
default:
json := core.JSONMarshalString(st)
var workspace WorkspaceStatus
if r := core.JSONUnmarshalString(json, &workspace); r.OK {
ws = &workspace
statusJSON := core.JSONMarshalString(status)
var decodedWorkspace WorkspaceStatus
if result := core.JSONUnmarshalString(statusJSON, &decodedWorkspace); result.OK {
workspaceStatus = &decodedWorkspace
}
}
if ws == nil {
if workspaceStatus == nil {
return
}
s.workspaces.Set(name, ws)
s.workspaces.Set(name, workspaceStatus)
// Remove pending reservation now that the real workspace is tracked
s.workspaces.Delete(core.Concat("pending/", ws.Repo))
s.workspaces.Delete(core.Concat("pending/", workspaceStatus.Repo))
}
// Workspaces returns the workspace Registry.
//
// s.Workspaces().Each(func(name string, st *WorkspaceStatus) { ... })
// s.Workspaces().Each(func(name string, workspaceStatus *WorkspaceStatus) { ... })
func (s *Service) Workspaces() *core.Registry[*WorkspaceStatus] {
return s.workspaces
}
// handleWorkspaceQuery answers workspace state queries from Core QUERY calls.
//
// r := c.QUERY(runner.WorkspaceQuery{Name: "core/go-io/task-42"})
// r := c.QUERY(runner.WorkspaceQuery{Status: "running"})
func (s *Service) handleWorkspaceQuery(_ *core.Core, q core.Query) core.Result {
wq, ok := q.(WorkspaceQuery)
// result := c.QUERY(runner.WorkspaceQuery{Name: "core/go-io/task-42"})
// result := c.QUERY(runner.WorkspaceQuery{Status: "running"})
func (s *Service) handleWorkspaceQuery(_ *core.Core, query core.Query) core.Result {
workspaceQuery, ok := query.(WorkspaceQuery)
if !ok {
return core.Result{}
}
if wq.Name != "" {
return s.workspaces.Get(wq.Name)
if workspaceQuery.Name != "" {
return s.workspaces.Get(workspaceQuery.Name)
}
if wq.Status != "" {
if workspaceQuery.Status != "" {
var names []string
s.workspaces.Each(func(name string, st *WorkspaceStatus) {
if st.Status == wq.Status {
s.workspaces.Each(func(name string, workspaceStatus *WorkspaceStatus) {
if workspaceStatus.Status == workspaceQuery.Status {
names = append(names, name)
}
})
@ -280,16 +280,16 @@ func (s *Service) handleWorkspaceQuery(_ *core.Core, q core.Query) core.Result {
// --- Actions ---
func (s *Service) actionDispatch(_ context.Context, opts core.Options) core.Result {
func (s *Service) actionDispatch(_ context.Context, options core.Options) core.Result {
if s.frozen {
return core.Result{Value: core.E("runner.actionDispatch", "queue is frozen", nil), OK: false}
}
agent := opts.String("agent")
agent := options.String("agent")
if agent == "" {
agent = "codex"
}
repo := opts.String("repo")
repo := options.String("repo")
s.dispatchMu.Lock()
defer s.dispatchMu.Unlock()
@ -313,8 +313,8 @@ func (s *Service) actionDispatch(_ context.Context, opts core.Options) core.Resu
func (s *Service) actionStatus(_ context.Context, _ core.Options) core.Result {
running, queued, completed, failed := 0, 0, 0, 0
s.workspaces.Each(func(_ string, st *WorkspaceStatus) {
switch st.Status {
s.workspaces.Each(func(_ string, workspaceStatus *WorkspaceStatus) {
switch workspaceStatus.Status {
case "running":
running++
case "queued":
@ -350,16 +350,16 @@ func (s *Service) actionKill(_ context.Context, _ core.Options) core.Result {
runtime = s.Core()
}
killed := 0
s.workspaces.Each(func(_ string, st *WorkspaceStatus) {
if st.Status == "running" && st.PID > 0 {
if agentic.ProcessTerminate(runtime, "", st.PID) {
s.workspaces.Each(func(_ string, workspaceStatus *WorkspaceStatus) {
if workspaceStatus.Status == "running" && workspaceStatus.PID > 0 {
if agentic.ProcessTerminate(runtime, "", workspaceStatus.PID) {
killed++
}
st.Status = "failed"
st.PID = 0
workspaceStatus.Status = "failed"
workspaceStatus.PID = 0
}
if st.Status == "queued" {
st.Status = "failed"
if workspaceStatus.Status == "queued" {
workspaceStatus.Status = "failed"
}
})
return core.Result{Value: core.Sprintf("killed %d agents", killed), OK: true}
@ -405,19 +405,19 @@ func (s *Service) hydrateWorkspaces() {
}
for _, path := range agentic.WorkspaceStatusPaths() {
wsDir := core.PathDir(path)
result := ReadStatusResult(wsDir)
if !result.OK {
statusResult := ReadStatusResult(wsDir)
if !statusResult.OK {
continue
}
st, ok := result.Value.(*WorkspaceStatus)
if !ok || st == nil {
workspaceStatus, ok := statusResult.Value.(*WorkspaceStatus)
if !ok || workspaceStatus == nil {
continue
}
// Re-queue running agents on restart — process is dead, re-dispatch
if st.Status == "running" {
st.Status = "queued"
if workspaceStatus.Status == "running" {
workspaceStatus.Status = "queued"
}
s.workspaces.Set(agentic.WorkspaceName(wsDir), st)
s.workspaces.Set(agentic.WorkspaceName(wsDir), workspaceStatus)
}
}
@ -442,7 +442,7 @@ type AgentNotification struct {
// WorkspaceQuery is the QUERY type for workspace lookups.
//
// r := c.QUERY(runner.WorkspaceQuery{Status: "running"})
// result := c.QUERY(runner.WorkspaceQuery{Status: "running"})
type WorkspaceQuery struct {
Name string
Status string
@ -450,7 +450,7 @@ type WorkspaceQuery struct {
// WorkspaceStatus tracks the state of an agent workspace.
//
// st := &runner.WorkspaceStatus{Status: "running", Agent: "codex", Repo: "go-io", PID: 12345}
// workspaceStatus := &runner.WorkspaceStatus{Status: "running", Agent: "codex", Repo: "go-io", PID: 12345}
type WorkspaceStatus struct {
Status string `json:"status"`
Agent string `json:"agent"`

View file

@ -43,9 +43,9 @@ func (s *Service) OnStartup(ctx context.Context) core.Result {
//
// remote := service.DetectGitRemote("/srv/repos/agent")
func (s *Service) DetectGitRemote(path string) string {
r := s.Core().Process().RunIn(context.Background(), path, "git", "remote", "get-url", "origin")
if !r.OK {
result := s.Core().Process().RunIn(context.Background(), path, "git", "remote", "get-url", "origin")
if !result.OK {
return ""
}
return parseGitRemote(core.Trim(r.Value.(string)))
return parseGitRemote(core.Trim(result.Value.(string)))
}

View file

@ -22,24 +22,24 @@ type Options struct {
//
// result := service.Run(setup.Options{Path: ".", Template: "auto"})
// core.Println(result.OK)
func (s *Service) Run(opts Options) core.Result {
if opts.Path == "" {
opts.Path = core.Env("DIR_CWD")
func (s *Service) Run(options Options) core.Result {
if options.Path == "" {
options.Path = core.Env("DIR_CWD")
}
opts.Path = absolutePath(opts.Path)
options.Path = absolutePath(options.Path)
projType := Detect(opts.Path)
allTypes := DetectAll(opts.Path)
projType := Detect(options.Path)
allTypes := DetectAll(options.Path)
core.Print(nil, "Project: %s", core.PathBase(opts.Path))
core.Print(nil, "Project: %s", core.PathBase(options.Path))
core.Print(nil, "Type: %s", projType)
if len(allTypes) > 1 {
core.Print(nil, "Also: %v (polyglot)", allTypes)
}
var tmplName string
if opts.Template != "" {
templateResult := resolveTemplateName(opts.Template, projType)
if options.Template != "" {
templateResult := resolveTemplateName(options.Template, projType)
if !templateResult.OK {
return templateResult
}
@ -53,28 +53,28 @@ func (s *Service) Run(opts Options) core.Result {
}
// Generate .core/ config files
if result := setupCoreDir(opts, projType); !result.OK {
if result := setupCoreDir(options, projType); !result.OK {
return result
}
// Scaffold from dir template if requested
if tmplName != "" {
return s.scaffoldTemplate(opts, projType, tmplName)
return s.scaffoldTemplate(options, projType, tmplName)
}
return core.Result{Value: opts.Path, OK: true}
return core.Result{Value: options.Path, OK: true}
}
// setupCoreDir creates .core/ with build.yaml and test.yaml.
func setupCoreDir(opts Options, projType ProjectType) core.Result {
coreDir := core.JoinPath(opts.Path, ".core")
func setupCoreDir(options Options, projType ProjectType) core.Result {
coreDir := core.JoinPath(options.Path, ".core")
if opts.DryRun {
if options.DryRun {
core.Print(nil, "")
core.Print(nil, "Would create %s/", coreDir)
} else {
if r := fs.EnsureDir(coreDir); !r.OK {
err, _ := r.Value.(error)
if ensureResult := fs.EnsureDir(coreDir); !ensureResult.OK {
err, _ := ensureResult.Value.(error)
return core.Result{
Value: core.E("setup.setupCoreDir", "create .core directory", err),
OK: false,
@ -83,7 +83,7 @@ func setupCoreDir(opts Options, projType ProjectType) core.Result {
}
// build.yaml
buildConfig := GenerateBuildConfig(opts.Path, projType)
buildConfig := GenerateBuildConfig(options.Path, projType)
if !buildConfig.OK {
err, _ := buildConfig.Value.(error)
return core.Result{
@ -91,7 +91,7 @@ func setupCoreDir(opts Options, projType ProjectType) core.Result {
OK: false,
}
}
if result := writeConfig(core.JoinPath(coreDir, "build.yaml"), buildConfig.Value.(string), opts); !result.OK {
if result := writeConfig(core.JoinPath(coreDir, "build.yaml"), buildConfig.Value.(string), options); !result.OK {
return result
}
@ -104,7 +104,7 @@ func setupCoreDir(opts Options, projType ProjectType) core.Result {
OK: false,
}
}
if result := writeConfig(core.JoinPath(coreDir, "test.yaml"), testConfig.Value.(string), opts); !result.OK {
if result := writeConfig(core.JoinPath(coreDir, "test.yaml"), testConfig.Value.(string), options); !result.OK {
return result
}
@ -112,29 +112,29 @@ func setupCoreDir(opts Options, projType ProjectType) core.Result {
}
// scaffoldTemplate extracts a dir template into the target path.
func (s *Service) scaffoldTemplate(opts Options, projType ProjectType, tmplName string) core.Result {
func (s *Service) scaffoldTemplate(options Options, projType ProjectType, tmplName string) core.Result {
core.Print(nil, "Template: %s", tmplName)
data := &lib.WorkspaceData{
Repo: core.PathBase(opts.Path),
Repo: core.PathBase(options.Path),
Branch: "main",
Task: core.Sprintf("Initialise %s project tooling.", projType),
Agent: "setup",
Language: string(projType),
Prompt: "This workspace was scaffolded by pkg/setup. Review the repository and continue from the generated context files.",
Flow: formatFlow(projType),
RepoDescription: s.DetectGitRemote(opts.Path),
RepoDescription: s.DetectGitRemote(options.Path),
BuildCmd: defaultBuildCommand(projType),
TestCmd: defaultTestCommand(projType),
}
if opts.DryRun {
core.Print(nil, "Would extract workspace/%s to %s", tmplName, opts.Path)
if options.DryRun {
core.Print(nil, "Would extract workspace/%s to %s", tmplName, options.Path)
core.Print(nil, " Template found: %s", tmplName)
return core.Result{Value: opts.Path, OK: true}
return core.Result{Value: options.Path, OK: true}
}
if result := lib.ExtractWorkspace(tmplName, opts.Path, data); !result.OK {
if result := lib.ExtractWorkspace(tmplName, options.Path, data); !result.OK {
if err, ok := result.Value.(error); ok {
return core.Result{
Value: core.E("setup.scaffoldTemplate", core.Concat("extract workspace template ", tmplName), err),
@ -146,22 +146,22 @@ func (s *Service) scaffoldTemplate(opts Options, projType ProjectType, tmplName
OK: false,
}
}
return core.Result{Value: opts.Path, OK: true}
return core.Result{Value: options.Path, OK: true}
}
func writeConfig(path, content string, opts Options) core.Result {
if opts.DryRun {
func writeConfig(path, content string, options Options) core.Result {
if options.DryRun {
core.Print(nil, " %s", path)
return core.Result{Value: path, OK: true}
}
if !opts.Force && fs.Exists(path) {
if !options.Force && fs.Exists(path) {
core.Print(nil, " skip %s (exists, use --force to overwrite)", core.PathBase(path))
return core.Result{Value: path, OK: true}
}
if r := fs.WriteMode(path, content, 0644); !r.OK {
err, _ := r.Value.(error)
if writeResult := fs.WriteMode(path, content, 0644); !writeResult.OK {
err, _ := writeResult.Value.(error)
return core.Result{
Value: core.E("setup.writeConfig", core.Concat("write ", core.PathBase(path)), err),
OK: false,