2026-03-16 11:10:33 +00:00
// SPDX-License-Identifier: EUPL-1.2
package agentic
import (
"context"
2026-03-22 03:41:07 +00:00
core "dappco.re/go/core"
2026-03-16 11:10:33 +00:00
"github.com/modelcontextprotocol/go-sdk/mcp"
)
2026-03-31 04:33:36 +00:00
// input := agentic.ResumeInput{Workspace: "core/go-scm/task-42", Answer: "Use the existing queue config"}
2026-03-16 11:10:33 +00:00
type ResumeInput struct {
2026-03-31 05:28:26 +00:00
Workspace string ` json:"workspace" `
Answer string ` json:"answer,omitempty" `
Agent string ` json:"agent,omitempty" `
DryRun bool ` json:"dry_run,omitempty" `
2026-03-16 11:10:33 +00:00
}
2026-03-31 04:33:36 +00:00
// out := agentic.ResumeOutput{Success: true, Workspace: "core/go-scm/task-42", Agent: "codex"}
2026-03-16 11:10:33 +00:00
type ResumeOutput struct {
refactor: migrate core/agent to Core primitives — reference implementation
Phase 1: go-io/go-log → core.Fs{}, core.E(), core.Error/Info/Warn
Phase 2: strings/fmt → core.Contains, core.Sprintf, core.Split etc
Phase 3: embed.FS → core.Mount/core.Embed, core.Extract
Phase 4: cmd/main.go → core.Command(), c.Cli().Run(), no cli package
All packages migrated:
- pkg/lib (Codex): core.Mount, core.Extract, Result returns, AX comments
- pkg/setup (Codex): core.Fs, core.E, fixed missing lib helpers
- pkg/brain (Codex): Core primitives, AX comments
- pkg/monitor (Codex): Core string/logging primitives
- pkg/agentic (Codex): 20 files, Core primitives throughout
- cmd/main.go: pure Core CLI, no fmt/log/filepath/strings/cli
Remaining stdlib: path/filepath (Core doesn't wrap OS paths),
fmt.Sscanf/strings.Map (no Core equivalent).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 06:13:41 +00:00
Success bool ` json:"success" `
Workspace string ` json:"workspace" `
Agent string ` json:"agent" `
PID int ` json:"pid,omitempty" `
OutputFile string ` json:"output_file,omitempty" `
Prompt string ` json:"prompt,omitempty" `
2026-03-16 11:10:33 +00:00
}
func ( s * PrepSubsystem ) registerResumeTool ( server * mcp . Server ) {
mcp . AddTool ( server , & mcp . Tool {
Name : "agentic_resume" ,
Description : "Resume a blocked agent workspace. Writes ANSWER.md if an answer is provided, then relaunches the agent with instructions to read it and continue." ,
} , s . resume )
}
func ( s * PrepSubsystem ) resume ( ctx context . Context , _ * mcp . CallToolRequest , input ResumeInput ) ( * mcp . CallToolResult , ResumeOutput , error ) {
if input . Workspace == "" {
2026-03-22 03:41:07 +00:00
return nil , ResumeOutput { } , core . E ( "resume" , "workspace is required" , nil )
2026-03-16 11:10:33 +00:00
}
2026-03-30 21:22:54 +00:00
workspaceDir := core . JoinPath ( WorkspaceRoot ( ) , input . Workspace )
repoDir := WorkspaceRepoDir ( workspaceDir )
2026-03-16 11:10:33 +00:00
refactor(agentic): workspace = clone, prompt replaces files
Major simplification of the dispatch model:
- Workspace dir: .core/workspace/{org}/{repo}/{pr|task|branch|tag}/
- Clone into repo/ (not src/), metadata in .meta/
- One of issue, pr, branch, or tag required for dispatch
- All context (brain, consumers, git log, wiki, plan) assembled
into prompt string — no TODO.md, PROMPT.md, CONTEXT.md files
- Resume detection: skip clone if repo/.git exists
- Default agent changed to codex
- spawnAgent drops srcDir param, runs from repo/
- No --skip-git-repo-check (repo/ IS a git repo)
- All downstream files: srcDir → repoDir
Track PRs, not workspace iterations.
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 13:41:59 +00:00
if ! fs . IsDir ( core . JoinPath ( repoDir , ".git" ) ) {
2026-03-26 06:38:02 +00:00
return nil , ResumeOutput { } , core . E ( "resume" , core . Concat ( "workspace not found: " , input . Workspace ) , nil )
2026-03-16 11:10:33 +00:00
}
2026-03-30 21:22:54 +00:00
result := ReadStatusResult ( workspaceDir )
2026-03-30 21:11:06 +00:00
workspaceStatus , ok := workspaceStatusValue ( result )
2026-03-30 19:40:02 +00:00
if ! ok {
err , _ := result . Value . ( error )
2026-03-22 03:41:07 +00:00
return nil , ResumeOutput { } , core . E ( "resume" , "no status.json in workspace" , err )
2026-03-16 11:10:33 +00:00
}
2026-03-30 21:11:06 +00:00
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 )
2026-03-16 11:10:33 +00:00
}
2026-03-30 21:11:06 +00:00
agent := workspaceStatus . Agent
2026-03-16 11:10:33 +00:00
if input . Agent != "" {
agent = input . Agent
}
if input . Answer != "" {
2026-03-30 21:22:54 +00:00
answerPath := workspaceAnswerPath ( workspaceDir )
refactor: migrate core/agent to Core primitives — reference implementation
Phase 1: go-io/go-log → core.Fs{}, core.E(), core.Error/Info/Warn
Phase 2: strings/fmt → core.Contains, core.Sprintf, core.Split etc
Phase 3: embed.FS → core.Mount/core.Embed, core.Extract
Phase 4: cmd/main.go → core.Command(), c.Cli().Run(), no cli package
All packages migrated:
- pkg/lib (Codex): core.Mount, core.Extract, Result returns, AX comments
- pkg/setup (Codex): core.Fs, core.E, fixed missing lib helpers
- pkg/brain (Codex): Core primitives, AX comments
- pkg/monitor (Codex): Core string/logging primitives
- pkg/agentic (Codex): 20 files, Core primitives throughout
- cmd/main.go: pure Core CLI, no fmt/log/filepath/strings/cli
Remaining stdlib: path/filepath (Core doesn't wrap OS paths),
fmt.Sscanf/strings.Map (no Core equivalent).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 06:13:41 +00:00
content := core . Sprintf ( "# Answer\n\n%s\n" , input . Answer )
2026-03-30 21:11:06 +00:00
if writeResult := fs . Write ( answerPath , content ) ; ! writeResult . OK {
err , _ := writeResult . Value . ( error )
2026-03-22 03:45:50 +00:00
return nil , ResumeOutput { } , core . E ( "resume" , "failed to write ANSWER.md" , err )
2026-03-16 11:10:33 +00:00
}
}
2026-03-30 21:11:06 +00:00
prompt := core . Concat ( "You are resuming previous work.\n\nORIGINAL TASK:\n" , workspaceStatus . Task )
2026-03-16 11:10:33 +00:00
if input . Answer != "" {
feat(v0.8.0): full AX migration — ServiceRuntime, Actions, quality gates, transport
go-process:
- Register factory, Result lifecycle, 5 named Action handlers
- Start/Run/StartWithOptions/RunWithOptions all return core.Result
- core.ID() replaces fmt.Sprintf, core.As replaces errors.As
core/agent:
- PrepSubsystem + monitor.Subsystem + setup.Service embed ServiceRuntime[T]
- 22 named Actions + agent.completion Task pipeline in OnStartup
- ChannelNotifier removed — all IPC via c.ACTION(messages.X{})
- proc.go: all methods via s.Core().Process(), returns core.Result
- status.go: WriteAtomic + JSONMarshalString
- paths.go: Fs.NewUnrestricted() replaces unsafe.Pointer
- transport.go: ONE net/http file — HTTPGet/HTTPPost/HTTPDo/MCP transport
- All disallowed imports eliminated from source files (13 quality gates)
- String concat eliminated — core.Concat() throughout
- 1:1 _test.go + _example_test.go for every source file
- Reference docs synced from core/go v0.8.0
- RFC-025 updated with net/http, net/url, io/fs quality gates
- lib.go: io/fs eliminated via Data.ListNames, Array[T].Deduplicate
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-26 01:27:46 +00:00
prompt = core . Concat ( prompt , "\n\nANSWER TO YOUR QUESTION:\n" , input . Answer )
2026-03-16 11:10:33 +00:00
}
feat(v0.8.0): full AX migration — ServiceRuntime, Actions, quality gates, transport
go-process:
- Register factory, Result lifecycle, 5 named Action handlers
- Start/Run/StartWithOptions/RunWithOptions all return core.Result
- core.ID() replaces fmt.Sprintf, core.As replaces errors.As
core/agent:
- PrepSubsystem + monitor.Subsystem + setup.Service embed ServiceRuntime[T]
- 22 named Actions + agent.completion Task pipeline in OnStartup
- ChannelNotifier removed — all IPC via c.ACTION(messages.X{})
- proc.go: all methods via s.Core().Process(), returns core.Result
- status.go: WriteAtomic + JSONMarshalString
- paths.go: Fs.NewUnrestricted() replaces unsafe.Pointer
- transport.go: ONE net/http file — HTTPGet/HTTPPost/HTTPDo/MCP transport
- All disallowed imports eliminated from source files (13 quality gates)
- String concat eliminated — core.Concat() throughout
- 1:1 _test.go + _example_test.go for every source file
- Reference docs synced from core/go v0.8.0
- RFC-025 updated with net/http, net/url, io/fs quality gates
- lib.go: io/fs eliminated via Data.ListNames, Array[T].Deduplicate
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-26 01:27:46 +00:00
prompt = core . Concat ( prompt , "\n\nContinue working. Read BLOCKED.md to see what you were stuck on. Commit when done." )
2026-03-16 11:10:33 +00:00
if input . DryRun {
return nil , ResumeOutput {
Success : true ,
Workspace : input . Workspace ,
Agent : agent ,
Prompt : prompt ,
} , nil
}
2026-03-30 21:22:54 +00:00
pid , processID , _ , err := s . spawnAgent ( agent , prompt , workspaceDir )
2026-03-16 11:10:33 +00:00
if err != nil {
return nil , ResumeOutput { } , err
}
2026-03-30 21:11:06 +00:00
workspaceStatus . Status = "running"
workspaceStatus . PID = pid
workspaceStatus . ProcessID = processID
workspaceStatus . Runs ++
workspaceStatus . Question = ""
2026-03-30 21:22:54 +00:00
writeStatusResult ( workspaceDir , workspaceStatus )
2026-03-16 11:10:33 +00:00
return nil , ResumeOutput {
Success : true ,
Workspace : input . Workspace ,
Agent : agent ,
2026-03-16 17:52:55 +00:00
PID : pid ,
2026-03-30 21:22:54 +00:00
OutputFile : agentOutputFile ( workspaceDir , agent ) ,
2026-03-16 11:10:33 +00:00
} , nil
}