feat: wire persist.Store into engine, elf manager, and agent tools

This commit is contained in:
2026-04-05 21:59:55 +02:00
parent eab5f26407
commit d251dd7507
7 changed files with 50 additions and 64 deletions

View File

@@ -6,6 +6,7 @@ import (
"fmt"
"io"
"log/slog"
mrand "math/rand"
"os"
"os/signal"
"strings"
@@ -13,6 +14,7 @@ import (
"somegit.dev/Owlibou/gnoma/internal/engine"
"encoding/json"
"somegit.dev/Owlibou/gnoma/internal/tool/persist"
gnomacfg "somegit.dev/Owlibou/gnoma/internal/config"
gnomactx "somegit.dev/Owlibou/gnoma/internal/context"
"somegit.dev/Owlibou/gnoma/internal/message"
@@ -273,6 +275,14 @@ func main() {
}
permChecker := permission.NewChecker(permission.Mode(*permMode), permRules, pipePromptFn)
// Generate session-scoped ID for /tmp artifact directory
sessionID := fmt.Sprintf("%s-%06x",
time.Now().Format("20060102-150405"),
mrand.Int63()&0xffffff,
)
store := persist.New(sessionID)
logger.Debug("session store initialized", "dir", store.Dir())
// Create elf manager and register agent tools.
// Must be created after fw and permChecker so elfs inherit security layers.
elfMgr := elf.NewManager(elf.ManagerConfig{
@@ -280,13 +290,14 @@ func main() {
Tools: reg,
Permissions: permChecker,
Firewall: fw,
Store: store,
Logger: logger,
})
elfProgressCh := make(chan elf.Progress, 16)
agentTool := agent.New(elfMgr)
agentTool := agent.New(elfMgr, store)
agentTool.SetProgressCh(elfProgressCh)
reg.Register(agentTool)
batchTool := agent.NewBatch(elfMgr)
batchTool := agent.NewBatch(elfMgr, store)
batchTool.SetProgressCh(elfProgressCh)
reg.Register(batchTool)
@@ -337,7 +348,8 @@ func main() {
System: systemPrompt,
Model: *model,
MaxTurns: *maxTurns,
Logger: logger,
Store: store,
Logger: logger,
})
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)

View File

@@ -1,53 +0,0 @@
package context
import (
"fmt"
"os"
"path/filepath"
"strings"
)
const (
// DefaultMaxResultSize is the threshold for persisting tool results.
DefaultMaxResultSize = 50_000 // chars
// PreviewSize is the number of chars to show inline.
PreviewSize = 2000
// ToolResultsDir is the subdirectory for persisted results.
ToolResultsDir = "tool-results"
)
// PersistLargeResult checks if a tool result exceeds the size limit.
// If so, writes it to disk and returns a preview + file path.
// Otherwise returns the original content unchanged.
func PersistLargeResult(content, toolUseID, sessionDir string) (string, bool) {
if len(content) <= DefaultMaxResultSize {
return content, false
}
// Create directory
dir := filepath.Join(sessionDir, ToolResultsDir)
os.MkdirAll(dir, 0o755)
// Write full result to disk
filename := toolUseID + ".txt"
path := filepath.Join(dir, filename)
os.WriteFile(path, []byte(content), 0o644)
// Build preview
preview := content
if len(preview) > PreviewSize {
preview = preview[:PreviewSize]
}
return fmt.Sprintf("<persisted-output>\nFull output saved to: %s\n\nPreview (first %d chars):\n%s\n</persisted-output>",
path, PreviewSize, preview), true
}
// TruncateToolResult truncates a tool result to a maximum size with an indicator.
func TruncateToolResult(content string, maxSize int) string {
if len(content) <= maxSize {
return content
}
lines := strings.Split(content[:maxSize], "\n")
return strings.Join(lines, "\n") + fmt.Sprintf("\n\n... (truncated, %d total chars)", len(content))
}

View File

@@ -12,6 +12,7 @@ import (
"somegit.dev/Owlibou/gnoma/internal/router"
"somegit.dev/Owlibou/gnoma/internal/security"
"somegit.dev/Owlibou/gnoma/internal/tool"
"somegit.dev/Owlibou/gnoma/internal/tool/persist"
)
// elfMeta tracks routing metadata and pool reservations for quality feedback.
@@ -30,6 +31,7 @@ type Manager struct {
tools *tool.Registry
permissions *permission.Checker
firewall *security.Firewall
store *persist.Store
logger *slog.Logger
}
@@ -38,6 +40,7 @@ type ManagerConfig struct {
Tools *tool.Registry
Permissions *permission.Checker // nil = allow all (unsafe; prefer passing parent checker)
Firewall *security.Firewall // nil = no scanning
Store *persist.Store // nil = no result persistence for elfs
Logger *slog.Logger
}
@@ -53,6 +56,7 @@ func NewManager(cfg ManagerConfig) *Manager {
tools: cfg.Tools,
permissions: cfg.Permissions,
firewall: cfg.Firewall,
store: cfg.Store,
logger: logger,
}
}
@@ -96,6 +100,7 @@ func (m *Manager) Spawn(ctx context.Context, taskType router.TaskType, prompt, s
System: systemPrompt,
Model: arm.ModelName,
MaxTurns: maxTurns,
Store: m.store,
Logger: m.logger,
})
if err != nil {
@@ -152,6 +157,7 @@ func (m *Manager) SpawnWithProvider(prov provider.Provider, model, prompt, syste
System: systemPrompt,
Model: model,
MaxTurns: maxTurns,
Store: m.store,
Logger: m.logger,
})
if err != nil {

View File

@@ -12,6 +12,7 @@ import (
"somegit.dev/Owlibou/gnoma/internal/router"
"somegit.dev/Owlibou/gnoma/internal/security"
"somegit.dev/Owlibou/gnoma/internal/tool"
"somegit.dev/Owlibou/gnoma/internal/tool/persist"
)
// Config holds engine configuration.
@@ -25,6 +26,7 @@ type Config struct {
System string // system prompt
Model string // override model (empty = provider default)
MaxTurns int // safety limit on tool loops (0 = unlimited)
Store *persist.Store // nil = no result persistence
Logger *slog.Logger
}

View File

@@ -14,6 +14,7 @@ import (
"somegit.dev/Owlibou/gnoma/internal/router"
"somegit.dev/Owlibou/gnoma/internal/stream"
"somegit.dev/Owlibou/gnoma/internal/tool"
"somegit.dev/Owlibou/gnoma/internal/tool/persist"
)
// Submit sends a user message and runs the agentic loop to completion.
@@ -394,10 +395,12 @@ func (e *Engine) executeSingleTool(ctx context.Context, call message.ToolCall, t
output = e.cfg.Firewall.ScanToolResult(output)
}
// Persist large results to disk
if persisted, ok := gnomactx.PersistLargeResult(output, call.ID, ".gnoma/sessions"); ok {
e.logger.Debug("tool result persisted to disk", "name", call.Name, "size", len(output))
output = persisted
// Persist results to /tmp for cross-tool session sharing
if e.cfg.Store != nil {
if path, ok := e.cfg.Store.Save(call.Name, call.ID, output); ok {
e.logger.Debug("tool result persisted", "name", call.Name, "path", path)
output = persist.InlineReplacement(path, output)
}
}
// Emit tool result event for the UI

View File

@@ -11,6 +11,7 @@ import (
"somegit.dev/Owlibou/gnoma/internal/router"
"somegit.dev/Owlibou/gnoma/internal/stream"
"somegit.dev/Owlibou/gnoma/internal/tool"
"somegit.dev/Owlibou/gnoma/internal/tool/persist"
)
var paramSchema = json.RawMessage(`{
@@ -37,10 +38,11 @@ var paramSchema = json.RawMessage(`{
type Tool struct {
manager *elf.Manager
ProgressCh chan<- elf.Progress // optional: sends structured progress to TUI
store *persist.Store
}
func New(mgr *elf.Manager) *Tool {
return &Tool{manager: mgr}
func New(mgr *elf.Manager, store *persist.Store) *Tool {
return &Tool{manager: mgr, store: store}
}
// SetProgressCh sets the channel for forwarding elf progress to the TUI.
@@ -80,6 +82,12 @@ func (t *Tool) Execute(ctx context.Context, args json.RawMessage) (tool.Result,
systemPrompt := "You are an elf — a focused sub-agent of gnoma. Complete the given task thoroughly and concisely. Use tools as needed."
var preSave []persist.ResultFile
if t.store != nil {
preSave, _ = t.store.List("")
}
_ = preSave // used in Task 4 for ResultFilePaths diff
e, err := t.manager.Spawn(ctx, taskType, a.Prompt, systemPrompt, maxTurns)
if err != nil {
return tool.Result{Output: fmt.Sprintf("Failed to spawn elf: %v", err)}, nil

View File

@@ -11,6 +11,7 @@ import (
"somegit.dev/Owlibou/gnoma/internal/elf"
"somegit.dev/Owlibou/gnoma/internal/stream"
"somegit.dev/Owlibou/gnoma/internal/tool"
"somegit.dev/Owlibou/gnoma/internal/tool/persist"
)
var batchSchema = json.RawMessage(`{
@@ -49,10 +50,11 @@ var batchSchema = json.RawMessage(`{
type BatchTool struct {
manager *elf.Manager
progressCh chan<- elf.Progress
store *persist.Store
}
func NewBatch(mgr *elf.Manager) *BatchTool {
return &BatchTool{manager: mgr}
func NewBatch(mgr *elf.Manager, store *persist.Store) *BatchTool {
return &BatchTool{manager: mgr, store: store}
}
func (t *BatchTool) SetProgressCh(ch chan<- elf.Progress) {
@@ -91,6 +93,12 @@ func (t *BatchTool) Execute(ctx context.Context, args json.RawMessage) (tool.Res
systemPrompt := "You are an elf — a focused sub-agent of gnoma. Complete the given task thoroughly and concisely. Use tools as needed."
var preSave []persist.ResultFile
if t.store != nil {
preSave, _ = t.store.List("")
}
_ = preSave // used in Task 4
// Spawn all elfs with slight stagger to avoid rate limit bursts
type elfEntry struct {
elf elf.Elf