mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-13 16:14:57 +00:00
Runs on models without a registered tokenizer (e.g. 9router brand models)
ended with the generic "Agent couldn't generate a response" fallback even
though the real request used about 55% of the context window.
PruneStage counted history with TokenCounter, which falls back to a
chars/2 heuristic for unregistered models and overcounted about 1.8x.
Once over budget it ran memory flush (~35s, invisible in traces), then
mid-loop compaction, which cannot summarize a history made only of tool
call/result pairs. The callback reported the untouched history as
compacted, PruneStage still saw it over budget and returned AbortRun
before any LLM call, and FinalizeStage replaced the empty reply with the
fallback.
- PruneStage and ContextStage overhead count with the request guard's
BudgetCounter. PruneStage no longer controls loop flow; the final
request guard in ThinkStage decides.
- CompactMessages returns ErrNotCompacted when history is unchanged.
Callers stop counting it as a compaction and do not retry it in the
same run, while post-run summarization still sees the pressure.
- When the guard exhausts every reduction step, ThinkStage stops the run
with a localized chat.context_budget_exceeded notice instead of an
error, so the run's tool results are still persisted. The stop reason
marks the trace and agent span as error; team tasks, cron and
heartbeat treat it as a failure via RunOutcome.Failure().
- Memory flush and mid-loop compaction emit event spans.
- Web and desktop UIs treat an unset context_pruning as enabled (the
backend default since 7639a8c0), keep it unset when untouched, and can
re-enable pruning after it was turned off.
146 lines
4.5 KiB
Go
146 lines
4.5 KiB
Go
package pipeline
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
|
|
"github.com/google/uuid"
|
|
|
|
"github.com/nextlevelbuilder/goclaw/internal/bus"
|
|
"github.com/nextlevelbuilder/goclaw/internal/providers"
|
|
"github.com/nextlevelbuilder/goclaw/internal/workspace"
|
|
)
|
|
|
|
// RunState is the shared mutable state for a single pipeline run.
|
|
// Passed by pointer through all stages.
|
|
type RunState struct {
|
|
// Identity (set once at pipeline start, immutable during run)
|
|
Input *RunInput
|
|
Workspace *workspace.WorkspaceContext
|
|
Model string
|
|
Provider providers.Provider
|
|
|
|
// Ctx holds enriched context from ContextStage (agent/user/workspace values).
|
|
// Pipeline.Run uses this for all stages after setup completes.
|
|
Ctx context.Context
|
|
|
|
// Message buffer (read/write by multiple stages)
|
|
Messages *MessageBuffer
|
|
|
|
// Per-stage substates
|
|
Context ContextState
|
|
Think ThinkState
|
|
Prune PruneState
|
|
Tool ToolState
|
|
Observe ObserveState
|
|
Compact CompactState
|
|
Evolution EvolutionState
|
|
|
|
// Cross-cutting concerns
|
|
Iteration int
|
|
RunID string
|
|
ExitCode StageResult
|
|
// StopReason explains a run stopped by the pipeline instead of the model (empty otherwise).
|
|
StopReason string
|
|
|
|
// CurrentLLMSpanID is the most recent LLM-call span in this run; tool spans parent to it.
|
|
CurrentLLMSpanID *uuid.UUID
|
|
// CurrentToolSpanID is the most recent tool-call span; post-tool-use hook spans parent to it.
|
|
CurrentToolSpanID *uuid.UUID
|
|
|
|
// Calls is the per-call usage breakdown (LLM calls + tool-internal LLM calls),
|
|
// appended during the run. Guarded by callsMu for the parallel tool path.
|
|
Calls []providers.CallUsage
|
|
callsMu sync.Mutex
|
|
}
|
|
|
|
// NewRunState creates a RunState with identity fields set.
|
|
func NewRunState(input *RunInput, ws *workspace.WorkspaceContext, model string, provider providers.Provider) *RunState {
|
|
return &RunState{
|
|
Input: input,
|
|
Workspace: ws,
|
|
Model: model,
|
|
Provider: provider,
|
|
RunID: input.RunID,
|
|
Messages: NewMessageBuffer(providers.Message{}),
|
|
}
|
|
}
|
|
|
|
// AppendCall records one call's usage in the run breakdown (thread-safe).
|
|
func (rs *RunState) AppendCall(c providers.CallUsage) {
|
|
rs.callsMu.Lock()
|
|
rs.Calls = append(rs.Calls, c)
|
|
rs.callsMu.Unlock()
|
|
}
|
|
|
|
// BuildResult converts final RunState into a RunResult.
|
|
func (rs *RunState) BuildResult() *RunResult {
|
|
return &RunResult{
|
|
RunID: rs.RunID,
|
|
Content: rs.Observe.FinalContent,
|
|
Thinking: rs.Observe.FinalThinking,
|
|
TotalUsage: rs.Think.TotalUsage,
|
|
LastUsage: rs.Think.LastUsage,
|
|
Iterations: rs.Iteration,
|
|
ToolCalls: rs.Tool.TotalToolCalls,
|
|
LoopKilled: rs.Tool.LoopKilled,
|
|
AsyncToolCalls: rs.Tool.AsyncToolCalls,
|
|
MediaResults: rs.Tool.MediaResults,
|
|
Deliverables: rs.Tool.Deliverables,
|
|
BlockReplies: rs.Observe.BlockReplies,
|
|
LastBlockReply: rs.Observe.LastBlockReply,
|
|
Calls: rs.Calls,
|
|
StopReason: rs.StopReason,
|
|
}
|
|
}
|
|
|
|
// RunInput is the pipeline's view of a run request.
|
|
// Converted from agent.RunRequest by the adapter in Phase 8.
|
|
type RunInput struct {
|
|
SessionKey string
|
|
Message string
|
|
Media []bus.MediaFile
|
|
ForwardMedia []bus.MediaFile
|
|
Channel string
|
|
ChannelType string
|
|
BitrixPortalDomain string // bitrix24-only: portal domain for entity URL construction
|
|
ChatTitle string
|
|
ChatID string
|
|
PeerKind string
|
|
RunID string
|
|
UserID string
|
|
SenderID string
|
|
SenderName string
|
|
Stream bool
|
|
ExtraSystemPrompt string
|
|
SkillFilter []string
|
|
HistoryLimit int
|
|
ToolAllow []string
|
|
TelegramManagerPermissions []string
|
|
LightContext bool
|
|
RunKind string
|
|
DelegationID string
|
|
TeamID string
|
|
TeamTaskID string
|
|
ParentAgentID string
|
|
MaxIterations int
|
|
ModelOverride string
|
|
HideInput bool
|
|
ContentSuffix string
|
|
LeaderAgentID string
|
|
WorkspaceChannel string
|
|
WorkspaceChatID string
|
|
TeamWorkspace string
|
|
}
|
|
|
|
// MediaResult represents a media file produced during tool execution.
|
|
type MediaResult struct {
|
|
Path string
|
|
ContentType string
|
|
Size int64
|
|
AsVoice bool
|
|
// Prompt is the generation prompt for AI-generated media (e.g. create_image).
|
|
// Empty for user-uploaded or non-generated files.
|
|
Prompt string
|
|
}
|