The Reasoning Architecture

How a question is put to a model affects the quality of the answer. Asking for step-by-step reasoning, showing worked examples, breaking a task into stages, letting the model use tools or exploring several lines of reasoning all improve results on different kinds of problem. These are known as prompting strategies.

The astra.langchain4j.reasoning package implements a set of prompting strategies as ASTRA agent classes. An agent adopts a strategy by extending its class and adopting the !solve(...) goal. Each strategy is written in ASTRA, so its control flow is a set of plans you can read, trace and build on.

The strategy classes

Every strategy extends LLMAgent, so an agent that uses one has a model module to initialize:

LLMAgent
├── BasicPrompting
├── ZeroShotChainOfThought
├── FewShotChainOfThought
├── Pipeline
│   └── MultiStepChainOfThought
├── ReActCore
│   ├── ReAct
│   └── ReActTools
│       ├── ReActMCP
│       └── ReActCapabilities
├── ThreadOfThoughts
└── TreeOfThoughts

Class

Strategy

Model calls

Configured by

BasicPrompting

Ask for the answer directly

1

—

ZeroShotChainOfThought

Ask the model to reason step by step before answering

1, or 2 if the answer must be extracted

—

FewShotChainOfThought

Show worked examples, then ask the question

1

example(question, reasoning, answer) beliefs

Pipeline

Run a sequence of steps, each a separate call that sees the earlier outputs

One per step

pipeline(list) and step(name, instruction) beliefs

MultiStepChainOfThought

A pipeline of reasoning steps followed by answer extraction

One per step, plus 1

Default steps, or your own pipeline and step beliefs

ReAct

Alternate between reasoning and calling tools

One per turn, up to maxSteps

tool(name, description) beliefs and +!invoke(...) plans

ThreadOfThoughts

Work through a long context one part at a time, keeping notes

One per part, plus 1

The list of parts passed to !solve

TreeOfThoughts

Propose and score alternative steps, keeping the best paths

Many; see Tree of Thoughts

branches, beam and depth beliefs

The solve contract

Each strategy handles the same goal:

!solve(string question, string answer)

The question is bound when the goal is adopted, and the answer is bound when the goal completes. ThreadOfThoughts adds a list of context parts: !solve(question, parts, answer). Pipeline offers a lower-level goal, !runPipeline(input, transcript), which returns the outputs of all its steps rather than an answer.

Because the goal is the same, changing strategy is a matter of changing the class an agent extends. This agent answers a question directly:

import astra.langchain4j.reasoning.*;

agent Direct extends BasicPrompting {
    module System S;

    rule +!main(list args) {
        try {
            model.loadApiKeyFromEnv("OPENAI_API_KEY");
            model.initialize("gpt-4o-mini",
                "astra.langchain4j.configuration.OpenAiConfiguration", []);

            !solve("Which planet is closest to the Sun?", string answer);
            console.println("Answer: " + answer);
        } recover {
            console.println("The question could not be answered.");
        }
        S.exit();
    }
}

Replacing BasicPrompting with ZeroShotChainOfThought makes the same agent reason step by step; nothing else changes. The strategy classes already declare console (a Console module), so the agent uses it without declaring its own.

Plans that use a strategy follow the same pattern:

  1. Initialize model before adopting !solve(...).

  2. Supply any beliefs the strategy needs, either as initial beliefs or by adding them before !solve(...) is adopted.

  3. Adopt !solve(...) inside try/recover, or handle ^!solve(...), so that a provider error does not leave the agent stuck.

Answers and fallbacks

The strategies ask the model to enclose its final answer, and nothing else, in <answer></answer> tags, then extract it with a response template. Models do not always follow the format. When the tags are missing, each strategy falls back rather than failing:

  • BasicPrompting, FewShotChainOfThought, MultiStepChainOfThought, ThreadOfThoughts and TreeOfThoughts use the whole reply as the answer.

  • ZeroShotChainOfThought makes a second call that asks the model to state the answer, following the original paper’s answer-extraction step.

  • ReAct asks the model to try again, and gives up after maxSteps turns.

An answer is therefore always text produced by the model. Check it before acting on it.

Run the examples

The examples on the following pages are in a single project. Each agent uses OpenAI through the OPENAI_API_KEY environment variable; change the model and configuration class to use another provider.

Download reasoning-agents.zip

Unzip it, set OPENAI_API_KEY and run an example by name:

mvn -Dastra.main=Direct

Example

Strategy

Page

Direct

BasicPrompting

Prompting and chain of thought

ZeroShot

ZeroShotChainOfThought

Prompting and chain of thought

FewShot

FewShotChainOfThought

Prompting and chain of thought

Steps

MultiStepChainOfThought

Pipelines

Tools

ReAct

ReAct: reasoning with tools

Passages

ThreadOfThoughts

Thread and Tree of Thoughts

Tree

TreeOfThoughts

Thread and Tree of Thoughts

Each run makes one or more remote model calls and can incur provider charges. Most strategies print the model’s intermediate output, so you can follow the reasoning as it happens. The answers vary between runs. To run the examples without a provider, use a scripted model.

Choosing a strategy

Start with the simplest strategy that works, and measure before moving to a more expensive one:

  • Use direct prompting for lookups, classification and short factual questions.

  • Use chain of thought when the answer requires a few steps of arithmetic or logic. Add few-shot examples when you need the model to reason in a particular way or produce a particular form of answer.

  • Use a pipeline when a task has distinct stages that benefit from separate attention, such as understand, plan, solve and check.

  • Use ReAct when the model needs information or actions it does not have, and the agent can provide them as tools.

  • Use Thread of Thoughts when the context is too long or too noisy to present at once.

  • Use Tree of Thoughts when early choices matter and a wrong step is hard to recover from, such as puzzles and planning problems.

The more elaborate strategies make more calls, take longer and cost more. A ReAct or TreeOfThoughts agent can make tens of calls for one question.

Origin of the reasoning agents

The reasoning agents were developed in the Pipelines project, which compares the strategies on the same problems, and moved into astra-langchain4j in ASTRA 2.3.0 so that applications can share them. Pipelines and the A2A example now use the library versions.

Choosing a ReAct implementation

The current ReAct family shares turn control through ReActCore. Select a subclass according to the interface through which the model requests actions.

Class

Model interface

Application responsibilities

ReAct

Text containing an action name and input

Advertise tools with beliefs and implement string-based !invoke handlers.

ReActTools

Structured tool calls with JSON arguments and call IDs

Register callable goals or tool declarations; supply remote execution when needed.

ReActMCP

Structured tool calls

Configure MCP connections; local callable goals use the same catalogue.

ReActCapabilities

Structured calls to semantic capability names

Bind MCP, UTCP, A2A or OpenAPI operations and register native callable goals.

ReActTools executes callable goals directly. Other local registrations use !invoke handlers; remote registrations need a !remoteTool implementation. A source-neutral ToolSource Java adapter can provide declarations and execution: the ToolSources module lets ASTRA inspect names, descriptions and schemas, then explicitly expose or hide individual tools. The Python benchmark bridge is an application adapter, not a Python dependency of the reasoning library.

Use text ReAct when an existing application deliberately controls a textual protocol. Use structured tools when schemas and provider tool-call messages suit the application. Use MCP for an MCP-specific integration, and capabilities when a conversation mixes implementations. None of these choices guarantees better task performance; evaluate them on the same tasks and model.

All these classes inherit run management. Structured tools can also consume scoped reasoning memory. See unified capabilities and MAMS integrations for the infrastructure choices.