fix: restore public Continue API; update steering docs for deferred persistence

Continue() was accidentally made internal (renamed to continueResponse).
Restore it as a public thin wrapper over continueResponse.

Update docs/steering.md and docs/design/steering-spec.md to reflect that
assistant replies are now saved to session history only after confirmed
channel delivery (OnDelivered), not synchronously at turn end.
This commit is contained in:
Dmitrii Balabanov 2026-03-31 22:01:33 +03:00
parent 7f61db81fb
commit e68e0d3488
3 changed files with 24 additions and 1 deletions

View file

@ -159,7 +159,7 @@ sequenceDiagram
AgentLoop-->>runLLMIteration: [] (empty, or messages) AgentLoop-->>runLLMIteration: [] (empty, or messages)
alt pendingMessages not empty alt pendingMessages not empty
runLLMIteration->>runLLMIteration: inject into messages[]<br/>save to session runLLMIteration->>runLLMIteration: inject into messages[]<br/>(saved to session on delivery)
end end
runLLMIteration->>LLM: Chat(messages, tools) 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. 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 ### Loop condition change
The iteration loop condition was changed from: The iteration loop condition was changed from:

View file

@ -102,6 +102,12 @@ if response == "" {
agent-scoped sessions continue on the correct agent instead of always using agent-scoped sessions continue on the correct agent instead of always using
the default one. 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 ## Polling points in the loop
Steering is checked at the following points in the agent cycle: Steering is checked at the following points in the agent cycle:

View file

@ -355,6 +355,18 @@ func (al *AgentLoop) continueResponse(
return al.continueWithSteeringMessages(ctx, agent, sessionKey, channel, chatID, steeringMsgs) 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 { func (al *AgentLoop) InterruptGraceful(hint string) error {
ts := al.getAnyActiveTurnState() ts := al.getAnyActiveTurnState()
if ts == nil { if ts == nil {