Adds the compounding knowledge loop the task model is built around: - complete_task(outcome, summary): a nomos-LOCAL, session-scoped tool (the shared MCP server has no session id). Introduces the local-tool mechanism — buildTools appends task tools, the agent loop routes them to handleTaskTool instead of the MCP client. Sets the task's terminal status/outcome/summary, mirrors it onto the task entity, and emits task.status. - Knowledge → task linkage: after a successful upsert_knowledge in a task, nomos links the note to the task entity (documents) and emits knowledge.recorded, so the task's outcome view shows what it learned. The note's about-link to the involved entity (written by upsert_knowledge) is the retrieval path future tasks use. - SOUL: every chat is a task loop — retrieve prior knowledge FIRST (get_entity_knowledge on the target), plan, execute, record learnings, then complete_task. Scales down for trivial read-only tasks. - deleteSession now cleans up the task entity, its relationships, and its task-scoped events (was orphaning them); the knowledge doc itself and its about-links survive, as knowledge should outlive the task. Verified end-to-end: a task recorded a note and completed; task.status + knowledge.recorded hit the SSE stream; status=done/outcome=success persisted; the note linked to both lxc:caddy (retrieval) and the task; a future get_entity_knowledge(lxc:caddy) surfaces it; delete cleaned edges+events (0/0/0) while the knowledge survived. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
73 lines
2.3 KiB
Go
73 lines
2.3 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
)
|
|
|
|
// Task tools are nomos-LOCAL, not MCP tools. They are session-scoped, and the
|
|
// shared MCP server (api:8090/mcp) has no session id — so these are handled
|
|
// in-process by nomos, which knows the session/task and holds the store.
|
|
// buildTools appends these to the model's tool list; the agent loop routes a
|
|
// call whose name isTaskTool to handleTaskTool instead of the MCP client.
|
|
//
|
|
// Phase 3 ships complete_task; set_goal / propose_plan / update_plan_step /
|
|
// ask_operator land in later phases through the same mechanism.
|
|
|
|
func taskToolDefs() []toolDef {
|
|
return []toolDef{
|
|
{
|
|
Name: "complete_task",
|
|
Description: "Mark the current task finished. Call this once the goal is " +
|
|
"verified done — or when you've genuinely failed or only partially " +
|
|
"succeeded. Sets the task's outcome and a one-line summary shown on the " +
|
|
"task board. Record what you learned with upsert_knowledge BEFORE " +
|
|
"completing, so future tasks on the same entities benefit.",
|
|
InputSchema: map[string]any{
|
|
"type": "object",
|
|
"properties": map[string]any{
|
|
"outcome": map[string]any{
|
|
"type": "string",
|
|
"enum": []string{"success", "failure", "partial"},
|
|
"description": "Did the task achieve its goal?",
|
|
},
|
|
"summary": map[string]any{
|
|
"type": "string",
|
|
"description": "One line describing the result (shown on the task card).",
|
|
},
|
|
},
|
|
"required": []string{"outcome", "summary"},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func isTaskTool(name string) bool {
|
|
switch name {
|
|
case "complete_task":
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// handleTaskTool executes a nomos-local task tool. Returns (result, true) if it
|
|
// handled the call, or (nil, false) if name is not a local task tool (so the
|
|
// caller forwards it to the MCP client).
|
|
func (a *agent) handleTaskTool(ctx context.Context, sessionID, name string, args map[string]any) (any, bool) {
|
|
switch name {
|
|
case "complete_task":
|
|
outcome, _ := args["outcome"].(string)
|
|
summary, _ := args["summary"].(string)
|
|
if outcome == "" {
|
|
outcome = "success"
|
|
}
|
|
if err := a.store.completeTask(ctx, sessionID, outcome, summary); err != nil {
|
|
return fmt.Sprintf("error completing task: %v", err), true
|
|
}
|
|
return fmt.Sprintf("Task marked %s: %s", outcome, summary), true
|
|
default:
|
|
return nil, false
|
|
}
|
|
}
|