TnsAI

Advanced Intelligence Patterns

Advanced cognitive capabilities in TnsAI.Intelligence for reasoning, memory consolidation, output validation, and iterative refinement.

Reasoning Strategies

TnsAI.Intelligence provides three advanced reasoning executors that go beyond simple prompt-response patterns. Each implements a different strategy for exploring solution spaces.

ReActExecutor

The ReActExecutor implements the ReAct (Reason + Act + Observe) pattern where an LLM explicitly reasons about what to do, takes an action, observes the result, and repeats until the goal is achieved. This produces structured Thought/Action/Observation traces for interpretability and decision auditing.

Based on "ReAct: Synergizing Reasoning and Acting in Language Models" (Yao et al., 2022).

ReActExecutor react = ReActExecutor.builder()
    .llm(client)
    .actionHandler((name, input) -> {
        if ("search".equals(name)) return Optional.of(searchService.search(input));
        if ("calculate".equals(name)) return Optional.of(calculator.eval(input));
        return Optional.empty();
    })
    .maxSteps(10)
    .thoughtFormat(ThoughtFormat.STRUCTURED)
    .stopCondition(StopCondition.finalAnswerDetected())
    .availableActions("search, calculate")
    .timeout(Duration.ofMinutes(5))
    .build();

ReActResult result = react.execute("Find the population of Tokyo and compare it to NYC");

System.out.println("Answer: " + result.getFinalAnswer());
System.out.println("Status: " + result.getStatus());    // SUCCESS, TIMEOUT, ERROR, MAX_STEPS_REACHED, STOPPED
System.out.println("Steps: " + result.getSteps().size());
System.out.println("LLM calls: " + result.getTotalLLMCalls());
System.out.println("Duration: " + result.getDuration());

// Inspect individual reasoning steps
for (ReActStep step : result.getSteps()) {
    System.out.println("Step " + step.getStepNumber());
    System.out.println("  Thought: " + step.getThought());
    System.out.println("  Action: " + step.getAction());
    System.out.println("  Observation: " + step.getObservation());
}

ThoughtFormat options: STRUCTURED (formal Thought/Action/Observation format) or FREE_FORM (open-ended reasoning). The ActionHandler is a @FunctionalInterface that takes an action name and input, returning Optional<String> (empty means action not available).

ReActStatus values: SUCCESS, TIMEOUT, ERROR, MAX_STEPS_REACHED, STOPPED.

TreeOfThoughtsExecutor

The TreeOfThoughtsExecutor explores multiple reasoning paths by generating candidate thoughts at each step, evaluating them for promise, pruning low-quality branches, and continuing on promising paths.

Based on "Tree of Thoughts: Deliberate Problem Solving with Large Language Models" (Yao et al., 2023).

TreeOfThoughtsExecutor tot = TreeOfThoughtsExecutor.builder()
    .llm(client)
    .evaluator(BranchEvaluator.llm(evalClient))
    .pruning(PruningStrategy.BEAM_SEARCH)
    .beamWidth(3)
    .maxDepth(5)
    .branchingFactor(3)
    .pruneThreshold(0.3)
    .timeout(Duration.ofMinutes(10))
    .build();

ToTResult result = tot.explore("Design a REST API for a todo app");

System.out.println("Best path: " + result.getBestPath());
System.out.println("Total nodes: " + result.getTotalNodes());
System.out.println("Pruned: " + result.getPrunedNodes());
System.out.println("Max depth reached: " + result.getMaxDepthReached());

PruningStrategy options:

StrategyBehavior
BEAM_SEARCHKeep top-K nodes by score at each level (default)
BEST_FIRSTSort by cumulative score, keep top-K
GREEDYKeep only the single best node
DEPTH_LIMITEDNo score-based pruning, only depth limit
EXHAUSTIVENo pruning at all

GraphOfThoughtsExecutor

The GraphOfThoughtsExecutor extends Tree of Thoughts by allowing cycles and merging of thought branches. Better for problems where partial solutions can be combined.

Based on "Graph of Thoughts" (Besta et al., 2023).

GraphOfThoughtsExecutor got = GraphOfThoughtsExecutor.builder()
    .llm(client)
    .evaluator(BranchEvaluator.llm(client))
    .operations(List.of(GoTOperation.GENERATE, GoTOperation.AGGREGATE, GoTOperation.REFINE))
    .maxNodes(20)
    .branchingFactor(3)
    .timeout(Duration.ofMinutes(10))
    .build();

GoTResult result = got.explore("Design a database schema for e-commerce");

System.out.println("Best thought: " + result.getBestThought());
System.out.println("Aggregated insight: " + result.aggregatedInsight());
System.out.println("Total nodes: " + result.totalNodes());
System.out.println("Merges performed: " + result.mergeCount());
System.out.println("Duration: " + result.duration());

GoTOperation types: GENERATE (create child thoughts), AGGREGATE (merge top-scoring nodes into a unified solution), REFINE (improve existing thoughts), SCORE (evaluate nodes).

The executor uses a frontier-based exploration: each iteration generates children, refines promising nodes (score \> 0.3), and aggregates top nodes from different branches. The frontier is pruned to the top branchingFactor nodes each round.

Cross-reference: For basic reasoning configuration, see Reasoning.

Atom of Thought (AoT)

The AotProcessor implements the Atom of Thought reasoning technique that decomposes complex problems into independent "atoms" solved in parallel. Unlike Chain-of-Thought where errors in early steps propagate, AoT isolates atoms so one bad result does not ruin the answer.

Based on "Atom of Thoughts for Markov LLM Test-Time Scaling" (arXiv, Feb 2025).

Benefits and Trade-offs

  • +30-40% accuracy improvement on complex reasoning tasks
  • Error isolation between atoms
  • Parallel execution of independent atoms
  • Per-atom confidence tracking
  • Trade-off: +20-30% token usage due to multiple LLM calls

Usage

// Simple usage
AotProcessor aot = new AotProcessor(llmClient);
AotResult result = aot.process("Calculate compound interest for $1000 at 5% for 3 years");

System.out.println("Answer: " + result.getAnswer());
System.out.println("Confidence: " + result.getConfidence() + "%");

// With configuration
AotProcessor aot = AotProcessor.builder()
    .llmClient(llmClient)
    .maxAtoms(8)
    .parallelExecution(true)
    .minConfidence(40f)
    .complexityThreshold(50)
    .synthesisStrategy(AtomSynthesizer.SynthesisStrategy.WEIGHTED_COMBINATION)
    .build();

AotResult result = aot.process(complexQuestion);
System.out.println(result.getDetailedTrace());

// Force AoT even for simple questions
AotResult forced = aot.processForced("What is 2+2?");

// Check question complexity before processing
int score = aot.getComplexityScore("Compare Python and Java for web development");

How It Works

  1. Complexity check: The processor scores the question for complexity (multi-step indicators, calculation keywords, comparison keywords). Below the threshold, it processes as a single atom.
  2. AtomGenerator: Decomposes the problem into atoms. Tries heuristic patterns first (calculation, comparison, list), then falls back to LLM-based decomposition. Each atom has an id, description, prompt, type (REASONING, RETRIEVAL, COMPUTATION, VALIDATION, SYNTHESIS), dependencies, and priority.
  3. AtomSolver: Solves atoms in parallel with dependency resolution. Groups atoms by level (independent atoms first, then dependent ones). Includes dependency context in prompts and extracts confidence scores from LLM responses.
  4. AtomSynthesizer: Combines atom solutions into a final answer using the configured synthesis strategy, weighting by confidence.

Call aot.shutdown() to release the executor thread pool when done.

Memory Consolidation

The MemoryConsolidationPipeline automates the transition from short-term conversation memory to long-term knowledge storage.

Pipeline Steps

  1. Extract knowledge from the session (via KnowledgeExtractor)
  2. Filter by confidence threshold
  3. Apply forgetting curve to older knowledge
  4. Store high-value knowledge (via KnowledgeStore)
  5. Compact conversation context if compactor provided
MemoryConsolidationPipeline pipeline = MemoryConsolidationPipeline.builder()
    .knowledgeExtractor(extractor)
    .contextCompactor(compactor)
    .knowledgeStore(store)
    .forgettingCurve(ForgettingCurve.EXPONENTIAL)
    .compactionConfig(compactionConfig)
    .minKnowledgeConfidence(0.5)
    .build();

ConsolidationResult result = pipeline.consolidate(session);
System.out.println("Extracted: " + result.knowledgeCount() + " knowledge items");
System.out.println("Original turns: " + result.originalCount());
System.out.println("Consolidated turns: " + result.consolidatedCount());
System.out.println("Summary: " + result.summary());
System.out.println("Duration: " + result.duration());

ForgettingCurve

Controls how memory importance decays over time when not accessed. Knowledge that decays below 0.1 is automatically deleted from the store.

CurveFormulaBehavior
NONENo decayAll memories retain full importance forever
LINEARscore * max(0, 1 - days * 0.01)Gradual linear decline, reaches zero after ~100 days
EXPONENTIALscore * e^(-0.05 * days)Ebbinghaus-inspired rapid initial decay that slows over time
double decayed = ForgettingCurve.EXPONENTIAL.decay(0.9, Duration.ofDays(7));

Cross-reference: For context management fundamentals, see Context.

Context Compaction

The ContextCompactor interface provides strategies for reducing conversation context size when approaching the token limit.

ContextCompactor Interface

public interface ContextCompactor {
    CompactionResult compact(List<Map<String, Object>> messages, CompactionConfig config);
    boolean shouldCompact(int currentTokens, int maxTokens, CompactionConfig config);
    int estimateTokens(List<Map<String, Object>> messages);
}

TwoPhaseCompactor

Combines truncation and LLM summarization in two phases for optimal context reduction.

  • Phase 1 (TruncatingCompactor): Truncates large tool-call arguments and tool-result content in older messages. Fast, requires no LLM call.
  • Phase 2 (LLMContextCompactor): Summarizes older messages using an LLM. Only runs if phase 1 alone is insufficient.
LLMClient summarizer = LLMClientFactory.create("openai", "gpt-4o-mini");
TwoPhaseCompactor compactor = new TwoPhaseCompactor(
    new TruncatingCompactor(200),       // max 200 chars per truncated field
    new LLMContextCompactor(summarizer)
);

CompactionConfig config = CompactionConfig.builder()
    .thresholdRatio(0.85)       // trigger compaction at 85% capacity
    .preserveLastN(5)           // keep last 5 messages intact
    .build();

if (compactor.shouldCompact(currentTokens, maxTokens, config)) {
    CompactionResult result = compactor.compact(messages, config);
    String summary = result.summary();
    int savedTokens = result.originalTokenCount() - result.compactedTokenCount();
}

Structured Output Validation

The StructuredOutputExecutor ensures LLM outputs conform to an expected schema with automatic retry on validation failure.

StructuredOutputExecutor<OrderSummary> executor = StructuredOutputExecutor.<OrderSummary>builder()
    .llm(client)
    .targetType(OrderSummary.class)
    .outputFormat(OutputFormat.JSON)
    .rules(List.of(
        ValidationRule.notNull("orderId"),
        ValidationRule.range("total", 0, 100000),
        ValidationRule.pattern("email", "^[\\w.-]+@[\\w.-]+$")
    ))
    .maxRetries(3)
    .systemPrompt("You are an order processing assistant.")
    .build();

StructuredOutputExecutor.StructuredOutputResult<OrderSummary> result =
    executor.generate("Summarize this order: ...");

if (result.success()) {
    OrderSummary order = result.value();
    System.out.println("Parsed in " + result.attempts() + " attempts");
} else {
    System.out.println("Failed after " + result.attempts() + " attempts");
    System.out.println("Errors: " + result.errors());
}

ValidationRule

Built-in validation rules for common checks:

Factory MethodBehavior
ValidationRule.notNull(fieldName)Field must exist and be non-null
ValidationRule.range(fieldName, min, max)Numeric field must be within range
ValidationRule.pattern(fieldName, regex)String field must match regex
ValidationRule.custom(description, predicate)Custom predicate on the entire data map

On validation failure, the executor sends a correction prompt containing the specific errors and re-requests a valid response, up to maxRetries times.

NormEngine

The NormEngine evaluates and enforces behavioral norms (obligations, prohibitions, permissions) on agent actions at runtime. Norms can be defined via @Norm annotations on role classes or programmatically.

Annotation-Based Norms

@Norms({
    @Norm(type = NormType.OBLIGATION, action = "logActivity",
          description = "Must log all activities", priority = 10),
    @Norm(type = NormType.PROHIBITION, action = "shareData",
          condition = "isConfidential",
          description = "Must not share confidential data", priority = 20),
    @Norm(type = NormType.PERMISSION, action = "readPublicData",
          description = "May read public data")
})
public class AnalystRole { }

NormEngine engine = NormEngine.fromAnnotations(AnalystRole.class);

Programmatic Norms

NormEngine engine = NormEngine.of(
    new NormEntry(NormType.PROHIBITION, "isConfidential", "shareData",
                  "Must not share confidential data", 20),
    new NormEntry(NormType.OBLIGATION, "", "logActivity",
                  "Must log all activities", 10)
);

// Add norms dynamically at runtime
engine.addNorm(new NormEntry(NormType.PERMISSION, "", "readData", "May read data", 5));

Checking Actions

// Check if an action is permitted (evaluates prohibitions)
NormEngine.CheckResult result = engine.checkAction("shareData",
    condition -> condition.equals("isConfidential") && context.isConfidential());

if (result.isViolation()) {
    for (NormViolation v : result.violations()) {
        System.out.println("Violated: " + v.norm().description());
    }
}

// Check for unfulfilled obligations
NormEngine.CheckResult obligations = engine.checkObligations(
    Set.of("readData"),       // actions already performed
    condition -> true          // condition evaluator
);

// Query active norms for current context
List<NormEntry> activeObligations = engine.getActiveObligations(cond -> true);
List<NormEntry> activeProhibitions = engine.getActiveProhibitions(cond -> true);
List<NormEntry> activePermissions = engine.getActivePermissions(cond -> true);
List<NormEntry> allNorms = engine.getAllNorms();

NormEntry Record

Fields: type() (NormType), condition(), action(), description(), priority(). The hasCondition() method returns true if the norm has a non-blank condition expression.

RefinementLoop

The RefinementLoop iteratively refines LLM outputs until they meet predefined quality standards. Inspired by Claude's Ralph plugin, it repeatedly evaluates output against completion criteria and re-prompts for corrections.

RefinementLoop loop = RefinementLoop.builder()
    .task("Convert Python to TypeScript")
    .completionCriteria(CompletionCriteria.builder()
        .compilerCheck("tsc --noEmit")
        .testCommand("npm test")
        .mustNotContain("def ", "import ")
        .mustContain("interface", "type")
        .customCheck("no-any", code -> !code.contains(": any"))
        .build())
    .maxIterations(10)
    .timeout(Duration.ofMinutes(30))
    .stopHook(StopHook.onAllTestsPass())
    .onIteration(iter -> log.info("Iteration {} score: {}",
        iter.iterationNumber(), iter.evaluation().overallScore()))
    .build();

RefinementResult result = loop.execute(agent, pythonCode);    // or loop.execute(llmClient, input)

System.out.println("Final output: " + result.getFinalOutput());
System.out.println("Iterations: " + result.getIterationCount());
System.out.println("Status: " + result.getStatus());         // SUCCESS, TIMEOUT, MAX_ITERATIONS, STOPPED, ERROR
System.out.println("Duration: " + result.getDuration());
System.out.println("Metrics: " + result.getMetrics());       // totalIterations, totalDurationMs, totalTokens, scoreProgression, finalScore

CompletionCriteria

Defines when the refinement loop should stop. Combines multiple check types:

CompletionCriteria criteria = CompletionCriteria.builder()
    .withLLM(llmClient)                        // required for llmCheck
    .compilerCheck("tsc --noEmit")             // shell command (exit 0 = pass)
    .testCommand("npm test")                   // shell command
    .lintCheck("eslint .")                     // shell command
    .mustContain("interface", "type")          // required content
    .mustNotContain("def ", "import ")         // forbidden content
    .matchesPattern("export default .*")       // regex match
    .wordCount(50, 5000)                       // word count range
    .minLines(10)                              // minimum line count
    .validJson()                               // valid JSON check
    .customCheck("no-any", code -> !code.contains(": any"))
    .llmCheck("Is this idiomatic TypeScript?", 0.8)  // LLM-based quality check
    .build();

EvaluationResult eval = criteria.evaluate(output);
boolean done = eval.allCriteriaMet();
double score = eval.overallScore();          // 0.0 - 1.0
List<CheckResult> passed = eval.passed();
List<CheckResult> failed = eval.failed();
List<String> reasons = eval.getFailureReasons();

RefinementStatus

Possible terminal states: SUCCESS (all criteria met), TIMEOUT, MAX_ITERATIONS, STOPPED (stop hook triggered), ERROR.

The refinement prompt includes failed checks as issues to fix and passed checks as elements to preserve, guiding the LLM toward incremental improvement.

On this page