post inicial

This commit is contained in:
lastdragon
2026-09-27 16:04:02 -06:00
parent d84587e1f3
commit 52864794f5
67 changed files with 15023 additions and 2 deletions
+141
View File
@@ -0,0 +1,141 @@
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}}
}