diff --git a/docs/design/steering-spec.md b/docs/design/steering-spec.md index 0951bf864..33e882bb1 100644 --- a/docs/design/steering-spec.md +++ b/docs/design/steering-spec.md @@ -159,7 +159,7 @@ sequenceDiagram AgentLoop-->>runLLMIteration: [] (empty, or messages) alt pendingMessages not empty - runLLMIteration->>runLLMIteration: inject into messages[]
save to session + runLLMIteration->>runLLMIteration: inject into messages[]
(saved to session on delivery) end runLLMIteration->>LLM: Chat(messages, tools) @@ -214,6 +214,11 @@ These results are: This ensures the LLM knows which of its requested actions were not performed. +> **Note:** the assistant reply that triggered the steering is saved to session +> history only after confirmed channel delivery (`OnDelivered`). Skipped tool +> results are saved synchronously as part of the turn because they are +> intermediate state within the same turn, not a final outbound response. + ### Loop condition change The iteration loop condition was changed from: diff --git a/docs/steering.md b/docs/steering.md index 63294ac5f..05a6ccdfc 100644 --- a/docs/steering.md +++ b/docs/steering.md @@ -102,6 +102,12 @@ if response == "" { agent-scoped sessions continue on the correct agent instead of always using the default one. +> **Note on session persistence:** the assistant reply produced by `Continue` +> is saved to session history only after the outbound message is confirmed +> delivered by the channel (via the internal `OnDelivered` callback). If the +> agent loop processes a fast follow-up before delivery completes, the pending +> reply is still visible in LLM context via the `pendingDeliveries` mechanism. + ## Polling points in the loop Steering is checked at the following points in the agent cycle: diff --git a/pkg/agent/steering.go b/pkg/agent/steering.go index 9fbc718d6..c10ad7b3e 100644 --- a/pkg/agent/steering.go +++ b/pkg/agent/steering.go @@ -355,6 +355,18 @@ func (al *AgentLoop) continueResponse( return al.continueWithSteeringMessages(ctx, agent, sessionKey, channel, chatID, steeringMsgs) } +// Continue is the public API for resuming an idle agent from external callers. +// It dequeues pending steering messages and runs them through the agent loop, +// returning the assistant reply content on success. +// If no steering messages are pending, it returns an empty string. +func (al *AgentLoop) Continue(ctx context.Context, sessionKey, channel, chatID string) (string, error) { + resp, err := al.continueResponse(ctx, sessionKey, channel, chatID) + if err != nil { + return "", err + } + return resp.Content, nil +} + func (al *AgentLoop) InterruptGraceful(hint string) error { ts := al.getAnyActiveTurnState() if ts == nil {