package tools import ( "context" "encoding/json" "errors" "fmt" "strings" "sysagent/internal/memory" ) // rememberTool guarda conocimiento persistente en memoria.md (§21, §81). // // Existe como herramienta dedicada (en lugar de write_file) para que la // memoria mantenga una estructura estable y sin duplicados (§82): el modelo // indica sección/entrada/hechos y el programa decide si añadir o actualizar. type rememberTool struct{} type rememberArgs struct { Section string `json:"section"` Entry string `json:"entry"` Facts []string `json:"facts"` Replace bool `json:"replace"` Sensitive bool `json:"sensitive"` } func (rememberTool) Name() string { return "remember" } func (rememberTool) Description() string { return "Save durable knowledge to memoria.md (not a log): hosts, IPs, ports, config paths, how to restart a service, OS/kernel, special dependencies, tools created, important decisions. " + "Facts as \"Label: value\" update the same label instead of duplicating. Do not store passwords: store where they are kept (e.g. \"Password: secret-tool lookup service x\"); set sensitive=true only if keeping the secret itself is strictly necessary." } func (rememberTool) Schema() map[string]any { return obj([]string{"section", "entry", "facts"}, map[string]any{ "section": str("top-level group, e.g. Hosts, Servicios, Sistema, Herramientas, Decisiones"), "entry": str("item inside the section, e.g. db01, nginx, kernel"), "facts": strList("facts, preferably \"Label: value\", e.g. [\"IP: 10.10.10.15\", \"Puerto: 5432\"]"), "replace": boolean("replace the whole entry instead of merging"), "sensitive": boolean("confirm that storing a credential is strictly necessary"), }) } func (rememberTool) Run(_ context.Context, env *Env, raw json.RawMessage) Result { var a rememberArgs if err := decode(raw, &a); err != nil { return fail("remember", err) } if env.Memory == nil { return fail("remember", errors.New("memory store not available")) } label := fmt.Sprintf("remember %s/%s", a.Section, a.Entry) if !a.Sensitive { for _, f := range a.Facts { if memory.LooksSecret(f) { return fail(label, fmt.Errorf("fact %q looks like a credential; store a reference to where it is kept, or set sensitive=true if keeping it is strictly necessary", clip(env.Log.Redact(f), 40))) } } } // La API key nunca se persiste (§58). facts := make([]string, len(a.Facts)) for i, f := range a.Facts { facts[i] = env.Log.Redact(f) } if env.DryRun { return dryRun(fmt.Sprintf("store in memoria.md under %s/%s: %s", a.Section, a.Entry, strings.Join(facts, "; "))) } res, err := env.Memory.Upsert(a.Section, a.Entry, facts, a.Replace) if err != nil { return fail(label, err) } out := Result{OK: true, ExitCode: 0, Summary: label} var b strings.Builder switch { case !res.Changed() && !a.Replace: b.WriteString("nothing changed: all facts were already stored") out.Summary += " (unchanged)" default: out.FilesChanged = []string{env.Memory.Path()} if res.Created { b.WriteString("new entry created\n") } for _, f := range res.Added { fmt.Fprintf(&b, "added: %s\n", f) } for _, f := range res.Updated { fmt.Fprintf(&b, "updated: %s\n", f) } for _, f := range res.Existing { fmt.Fprintf(&b, "already present: %s\n", f) } } if a.Sensitive { out.Audit = []string{"sensitive", "true"} } out.Output = b.String() return out } // forgetTool elimina conocimiento obsoleto o erróneo de memoria.md. type forgetTool struct{} type forgetArgs struct { Section string `json:"section"` Entry string `json:"entry"` Reason string `json:"reason"` } func (forgetTool) Name() string { return "forget" } func (forgetTool) Description() string { return "Remove an obsolete or wrong entry from memoria.md (or a whole section if entry is empty)." } func (forgetTool) Schema() map[string]any { return obj([]string{"section"}, map[string]any{ "section": str("section name"), "entry": str("entry name; empty removes the whole section"), "reason": str("why it is no longer valid (audit log)"), }) } func (forgetTool) Run(_ context.Context, env *Env, raw json.RawMessage) Result { var a forgetArgs if err := decode(raw, &a); err != nil { return fail("forget", err) } if env.Memory == nil { return fail("forget", errors.New("memory store not available")) } label := fmt.Sprintf("forget %s/%s", a.Section, a.Entry) if env.DryRun { return dryRun(label) } ok, err := env.Memory.Forget(a.Section, a.Entry) if err != nil { return fail(label, err) } if !ok { return Result{OK: true, ExitCode: 0, Summary: label + " (not found)", Output: "not found; nothing removed"} } return Result{OK: true, ExitCode: 0, Summary: label, Output: "removed", FilesChanged: []string{env.Memory.Path()}, Audit: []string{"reason", a.Reason}} }