refactor(daemon): remove context snapshot, let agent fetch data via CLI
Replace the frozen context snapshot pattern with a CLI-driven approach: agents now use `multica` CLI commands to fetch issue details, comments, and workspace context on demand, always getting the latest data. - Remove buildContextSnapshot and snapshot generation from enqueue - Claim endpoint now returns fresh agent name + skills from DB - Daemon resolves provider from local runtimeIndex, not snapshot - Prompt instructs agent to use `multica issue get` / `comment list` - Meta skill (CLAUDE.md/AGENTS.md) documents all available CLI commands - Skills still injected as filesystem files (static agent config) - Simplify daemon types: remove TaskContext/IssueContext/RuntimeContext Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
6733262a63
commit
1deae2a1e9
12 changed files with 176 additions and 238 deletions
|
|
@ -10,6 +10,7 @@ import (
|
|||
"github.com/go-chi/chi/v5"
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
"github.com/multica-ai/multica/server/internal/logger"
|
||||
"github.com/multica-ai/multica/server/internal/service"
|
||||
db "github.com/multica-ai/multica/server/pkg/db/generated"
|
||||
"github.com/multica-ai/multica/server/pkg/protocol"
|
||||
)
|
||||
|
|
@ -81,19 +82,27 @@ func agentToResponse(a db.Agent) AgentResponse {
|
|||
}
|
||||
|
||||
type AgentTaskResponse struct {
|
||||
ID string `json:"id"`
|
||||
AgentID string `json:"agent_id"`
|
||||
RuntimeID string `json:"runtime_id"`
|
||||
IssueID string `json:"issue_id"`
|
||||
Status string `json:"status"`
|
||||
Priority int32 `json:"priority"`
|
||||
DispatchedAt *string `json:"dispatched_at"`
|
||||
StartedAt *string `json:"started_at"`
|
||||
CompletedAt *string `json:"completed_at"`
|
||||
Result any `json:"result"`
|
||||
Error *string `json:"error"`
|
||||
Context any `json:"context,omitempty"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
ID string `json:"id"`
|
||||
AgentID string `json:"agent_id"`
|
||||
RuntimeID string `json:"runtime_id"`
|
||||
IssueID string `json:"issue_id"`
|
||||
Status string `json:"status"`
|
||||
Priority int32 `json:"priority"`
|
||||
DispatchedAt *string `json:"dispatched_at"`
|
||||
StartedAt *string `json:"started_at"`
|
||||
CompletedAt *string `json:"completed_at"`
|
||||
Result any `json:"result"`
|
||||
Error *string `json:"error"`
|
||||
Agent *TaskAgentData `json:"agent,omitempty"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
|
||||
// TaskAgentData holds agent info included in claim responses so the daemon
|
||||
// can set up the execution environment (branch naming, skill files).
|
||||
type TaskAgentData struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Skills []service.AgentSkillData `json:"skills,omitempty"`
|
||||
}
|
||||
|
||||
func taskToResponse(t db.AgentTaskQueue) AgentTaskResponse {
|
||||
|
|
@ -101,10 +110,6 @@ func taskToResponse(t db.AgentTaskQueue) AgentTaskResponse {
|
|||
if t.Result != nil {
|
||||
json.Unmarshal(t.Result, &result)
|
||||
}
|
||||
var ctx any
|
||||
if t.Context != nil {
|
||||
json.Unmarshal(t.Context, &ctx)
|
||||
}
|
||||
return AgentTaskResponse{
|
||||
ID: uuidToString(t.ID),
|
||||
AgentID: uuidToString(t.AgentID),
|
||||
|
|
@ -117,7 +122,6 @@ func taskToResponse(t db.AgentTaskQueue) AgentTaskResponse {
|
|||
CompletedAt: timestampToPtr(t.CompletedAt),
|
||||
Result: result,
|
||||
Error: textToPtr(t.Error),
|
||||
Context: ctx,
|
||||
CreatedAt: timestampToString(t.CreatedAt),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -144,6 +144,7 @@ func (h *Handler) DaemonHeartbeat(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
// ClaimTaskByRuntime atomically claims the next queued task for a runtime.
|
||||
// The response includes the agent's name and skills, fetched fresh from the DB.
|
||||
func (h *Handler) ClaimTaskByRuntime(w http.ResponseWriter, r *http.Request) {
|
||||
runtimeID := chi.URLParam(r, "runtimeId")
|
||||
|
||||
|
|
@ -159,8 +160,19 @@ func (h *Handler) ClaimTaskByRuntime(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
// Build response with fresh agent data (name + skills).
|
||||
resp := taskToResponse(*task)
|
||||
if agent, err := h.Queries.GetAgent(r.Context(), task.AgentID); err == nil {
|
||||
skills := h.TaskService.LoadAgentSkills(r.Context(), task.AgentID)
|
||||
resp.Agent = &TaskAgentData{
|
||||
ID: uuidToString(agent.ID),
|
||||
Name: agent.Name,
|
||||
Skills: skills,
|
||||
}
|
||||
}
|
||||
|
||||
slog.Info("task claimed by runtime", "task_id", uuidToString(task.ID), "runtime_id", runtimeID, "agent_id", uuidToString(task.AgentID))
|
||||
writeJSON(w, http.StatusOK, map[string]any{"task": taskToResponse(*task)})
|
||||
writeJSON(w, http.StatusOK, map[string]any{"task": resp})
|
||||
}
|
||||
|
||||
// ListPendingTasksByRuntime returns queued/dispatched tasks for a runtime.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue