Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 29 additions & 1 deletion docs/tools/think/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ _Step-by-step reasoning scratchpad for planning and decision-making._

The think tool is a reasoning scratchpad that lets agents think step-by-step before acting. The agent can write its thoughts without producing visible output to the user — ideal for planning complex tasks, breaking down problems, and reasoning through multi-step solutions.

This is a lightweight tool with no side effects. It is most useful for models that lack built-in reasoning or thinking capabilities (e.g., smaller or older models). For models that already support native thinking — such as Claude with extended thinking, OpenAI o-series, or Gemini with a thinking budget — this tool is unnecessary since the model can reason internally.
This lightweight tool changes only its internal scratchpad, not external data. It is most useful for models that lack built-in reasoning or thinking capabilities (e.g., smaller or older models). For models that already support native thinking — such as Claude with extended thinking, OpenAI o-series, or Gemini with a thinking budget — this tool is unnecessary since the model can reason internally.

## Configuration

Expand All @@ -28,3 +28,31 @@ No configuration options.
> **When to use**
>
> Use the think tool with models that don't have native reasoning capabilities. If your model already supports a [thinking budget](../../configuration/models/index.md#thinking-budget), you likely don't need this tool.

## Scope and lifetime

Thoughts are isolated by conversation ID, calling agent and think toolset instance.
They accumulate across turns, runtime closure and compaction. The built-in SQLite
session store retains raw thoughts across reloads and restarts; the in-memory
store retains them only for its lifetime. This internal state is stored before
response-output redaction, so session-database and backup privacy policies apply.

A branch or fork with a new conversation ID starts empty. A same-ID session clone
addresses the same durable scratchpad; cloning messages does not provide a
transactional copy of hidden thoughts. JSON export/import does not transport the
scratchpad, and existing conversations start with empty dedicated state on upgrade.

Toolset namespaces follow configuration order (configured and extra toolsets are
separate). Resuming with reordered toolsets or another configuration with the same
agent name can reassign these namespaces. Stable identity requires unchanged topology.

Root deletion removes all root-owned thoughts, including those from aborted
sub-sessions. Deleting a persisted child removes known subtree thoughts;
never-persisted descendants remain root-owned until root deletion. Stop writers
before deleting sessions. Deletion is logical, not secure erasure or backup removal.

There is no TTL, eviction, quota or output omission: every call returns the full
scratchpad, which can grow beyond model context limits. Storage and output memory
scale with retained thoughts. Persistence, ownership or unsupported-store failures
are tool errors, never silent resets. Only genuinely unscoped hosts, such as
pre-run prompt expansion, return an ephemeral single thought.
5 changes: 4 additions & 1 deletion pkg/runtime/loop.go
Original file line number Diff line number Diff line change
Expand Up @@ -283,20 +283,23 @@ func (r *LocalRuntime) runStream(ctx context.Context, sess *session.Session, idl
// the whole lifetime of its stream.
entry := r.registerLiveSessionWithIdleRetry(sess, idleRetryPolicy)

// Persist the root before tools can append root-owned state.
out := r.observe(ctx, sess, events)
go func() {
if rootStream {
defer r.activeRootStreams.Add(-1)
}
r.runStreamLoop(ctx, sess, entry, events)
}()
return r.observe(ctx, sess, events)
return out
}

// runStreamLoop is the body of RunStream. Pulled out of the anonymous
// goroutine so it has a real name in stack traces and is easier to navigate
// in editors.
func (r *LocalRuntime) runStreamLoop(ctx context.Context, sess *session.Session, liveEntry *liveSessionEntry, events chan Event) {
sink := &channelSink{ch: events}
ctx = context.WithValue(ctx, scratchpadRootKey{}, liveEntry.treeRootID)

// Seed the cagent session ID at the run-loop boundary so any
// gateway-bound HTTP call originating from this loop can correlate
Expand Down
Loading
Loading