Files
executus/tools/store.go
T
steve ac961e1539
executus CI / test (pull_request) Successful in 58s
Adversarial Review (Gadfly) / review (pull_request) Successful in 10m10s
P3: store group — kv_* + file_* tools (agent memory)
RegisterStore(reg, StoreDeps) registers the persistent-memory tools over the
host's KV and/or File backends:
- kv_get/set/list/delete (KVStorage seam)
- file_save/get/get_text/get_metadata/list/delete (FileStorage seam), plus
  file_search (FileSearcher) and create_file_url (FileTokenMinter) when wired.

Near-zero-config: Quota defaults to a generous static cap (staticQuota), the
per-value/per-file caps default, and the kv vs file groups register
independently (a host can take just one). Seams moved clean (interface-only):
kv_storage.go, quota_provider.go, file_descendant_grant.go. The default
in-memory KV/File backends come with contrib/store at P4.

Core go.sum still free of gorm/redis/discordgo/sqlite.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 22:06:46 -04:00

78 lines
2.4 KiB
Go

package tools
import (
"context"
"errors"
"gitea.stevedudenhoeffer.com/steve/executus/tool"
)
// StoreDeps wires the persistent-memory tools (kv_* and file_*). A host
// supplies its KV and/or File backends; the kv group registers only when KV is
// set and the file group only when Files is set, so a host can take just one.
// Everything else has a sensible default:
//
// - Quota defaults to a generous static cap (a host that meters per-skill
// storage supplies its own QuotaProvider).
// - FileSearch / Minter+BaseURL are optional — file_search and
// create_file_url register only when wired.
// - MaxValueBytes / MaxFileBytes default when non-positive.
type StoreDeps struct {
KV KVStorage
Files FileStorage
Quota QuotaProvider
FileSearch FileSearcher
Minter FileTokenMinter
BaseURL string
MaxValueBytes int // kv_set per-value cap; default 256 KiB
MaxFileBytes int // file_save per-file cap; default 16 MiB
}
// RegisterStore registers the kv_* tools (when KV is set) and the file_* tools
// (when Files is set). At least one of KV/Files is required.
func RegisterStore(reg tool.Registry, d StoreDeps) error {
if d.KV == nil && d.Files == nil {
return errors.New("tools: RegisterStore needs at least KV or Files")
}
if d.Quota == nil {
d.Quota = staticQuota{kvMax: 64 << 20, filesMax: 1 << 30}
}
if d.MaxValueBytes <= 0 {
d.MaxValueBytes = 256 << 10
}
if d.MaxFileBytes <= 0 {
d.MaxFileBytes = 16 << 20
}
var ts []tool.Tool
if d.KV != nil {
ts = append(ts,
NewKVGet(d.KV), NewKVSet(d.KV, d.Quota, d.MaxValueBytes),
NewKVList(d.KV), NewKVDelete(d.KV),
)
}
if d.Files != nil {
ts = append(ts,
NewFileSave(d.Files, d.Quota, d.MaxFileBytes),
NewFileGet(d.Files), NewFileGetText(d.Files), NewFileGetMetadata(d.Files),
NewFileList(d.Files), NewFileDelete(d.Files),
)
if d.FileSearch != nil {
ts = append(ts, NewFileSearch(d.FileSearch))
}
if d.Minter != nil && d.BaseURL != "" {
ts = append(ts, NewCreateFileURL(d.Minter, d.Files, d.BaseURL))
}
}
return registerAll(reg, ts...)
}
// staticQuota is the default QuotaProvider: a fixed KV/file byte cap for every
// skill. A host that needs per-skill metering supplies its own.
type staticQuota struct{ kvMax, filesMax int64 }
func (q staticQuota) EffectiveQuota(context.Context, string) (kvMax, filesMax int64, err error) {
return q.kvMax, q.filesMax, nil
}