{"slug": "reacttrace-in-solon-ai-turn-agent-runs-into-an-observable-boundary", "title": "ReActTrace in Solon AI: Turn Agent Runs into an Observable Boundary", "summary": "Solon AI's ReActTrace in v4.0.3 provides a runtime record for ReAct agents, exposing step counts, tool calls, pending states, and metrics for observability. The trace enables developers to monitor agent runs without exposing private reasoning, supporting interactive streaming and pending workflows.", "body_md": "ReAct agents are easier to trust when you can answer more than “what did the model say?” You also need to know how many turns ran, which tools were called, whether a plan existed, and whether the run is waiting for approval.\n\nSolon AI’s `ReActTrace`\n\nis the runtime record for that job. In Solon v4.0.3, it acts as the state-machine context behind a ReAct run: short-term memory, route state, message sequence, plans, tool-call counts, metrics, and pending state live behind one trace.\n\nA final answer is a poor production event. It tells you the outcome, but not whether the agent reached it efficiently or safely.\n\nA useful operational record should answer:\n\n`ReActTrace`\n\nexposes these concerns directly:\n\n```\nReActResponse response = agent.prompt(\"Investigate the failed payment and summarize the cause\")\n        .session(session)\n        .call();\n\nReActTrace trace = response.getTrace();\n\nSystem.out.println(\"steps=\" + trace.getStepCount());\nSystem.out.println(\"toolCalls=\" + trace.getToolCallCount());\nSystem.out.println(\"pending=\" + trace.isPending());\nSystem.out.println(\"pendingReason=\" + trace.getPendingReason());\nSystem.out.println(\"history=\" + trace.getFormattedHistory());\n```\n\nThe response also exposes `getMetrics()`\n\nfor execution indicators such as duration and token usage. Keep `getContent()`\n\nfor the user-facing answer; keep the trace and metrics for telemetry.\n\nWhen planning is enabled, the plan is not merely prompt text. The trace can expose the current plan and its formatted progress:\n\n```\nReActAgent agent = ReActAgent.of(chatModel)\n        .name(\"payment_investigator\")\n        .planningMode(true)\n        .maxTurns(8)\n        .defaultToolAdd(new PaymentTools())\n        .build();\n\nReActResponse response = agent.prompt(\"Investigate the failed payment\")\n        .session(session)\n        .call();\n\nReActTrace trace = response.getTrace();\nif (trace.hasPlans()) {\n    trace.getPlans().forEach(step -> System.out.println(\"plan=\" + step));\n    System.out.println(trace.getPlanProgress());\n}\n```\n\nThis gives a UI or audit pipeline a stable place to show progress without parsing the model’s prose. The official API also provides `getFormattedPlans()`\n\nwhen a formatted representation is more convenient.\n\nFor interactive applications, `stream()`\n\nemits typed agent chunks. Solon’s documentation distinguishes planning, reasoning, thought, action, observation, and final ReAct chunks.\n\nA UI can use those events as status signals:\n\n```\nagent.prompt(\"Check the latest payment status\")\n        .session(session)\n        .stream()\n        .doOnNext(chunk -> {\n            if (chunk instanceof PlanChunk) {\n                ui.showStatus(\"Planning\");\n            } else if (chunk instanceof ActionChunk) {\n                ui.showStatus(\"Calling a tool\");\n            } else if (chunk instanceof ObservationChunk) {\n                ui.showStatus(\"Processing tool result\");\n            } else if (chunk instanceof ReActChunk) {\n                ui.showAnswer(chunk.getContent());\n            }\n        })\n        .doOnError(ui::showError)\n        .blockLast();\n```\n\nThe important design choice is to render lifecycle status, tool names, and safe summaries—not to expose private reasoning text by default. Observability should help operators debug a run without turning internal deliberation into a user-facing contract.\n\nSome workflows must pause before a sensitive action. `ReActTrace`\n\nprovides `pending(String)`\n\n, `isPending()`\n\n, and `getPendingReason()`\n\nfor a run that is waiting rather than finished.\n\nThat distinction matters to an API layer:\n\n```\nReActResponse response = agent.prompt(\"Refund the customer\")\n        .session(session)\n        .call();\n\nReActTrace trace = response.getTrace();\nif (trace.isPending()) {\n    return new AgentStatus(\"PENDING\", trace.getPendingReason());\n}\n\nreturn new AgentStatus(\"COMPLETED\", response.getContent());\n```\n\nDo not report a pending run as a successful empty answer. Persist the session according to your application’s policy, show the pending reason to an authorized reviewer, and resume through the documented session flow after the decision is available.\n\nKeep framework-specific trace access in one adapter. This prevents controllers and dashboards from depending on every internal detail:\n\n```\npublic record AgentRunSummary(\n        String agent,\n        int steps,\n        int toolCalls,\n        boolean pending,\n        String pendingReason,\n        String answer) {\n\n    static AgentRunSummary from(ReActResponse response) {\n        ReActTrace trace = response.getTrace();\n        return new AgentRunSummary(\n                trace.getAgentName(),\n                trace.getStepCount(),\n                trace.getToolCallCount(),\n                trace.isPending(),\n                trace.getPendingReason(),\n                response.getContent());\n    }\n}\n```\n\nIn production, I would add a correlation ID from the surrounding request, record duration and token metrics, and redact tool arguments or observations that contain secrets or personal data. `getFormattedHistory()`\n\nis useful for controlled diagnostics, but it should not automatically become an unrestricted log line.\n\n`getContent()`\n\nto the user, not the raw trace.`isPending()`\n\nas a separate state from success and failure.`maxTurns`\n\nas a runtime guard even when planning is enabled.The useful mental model is simple: `ReActResponse`\n\nis the result envelope; `ReActTrace`\n\nis the run record. Once that boundary is explicit, agent observability becomes an engineering surface rather than a collection of prompt strings and console logs.\n\nOfficial references:", "url": "https://wpnews.pro/news/reacttrace-in-solon-ai-turn-agent-runs-into-an-observable-boundary", "canonical_source": "https://dev.to/solonjava/reacttrace-in-solon-ai-turn-agent-runs-into-an-observable-boundary-3lj0", "published_at": "2026-07-24 05:18:24+00:00", "updated_at": "2026-07-24 06:02:06.654905+00:00", "lang": "en", "topics": ["ai-agents", "developer-tools", "artificial-intelligence"], "entities": ["Solon AI", "ReActTrace", "ReActAgent"], "alternates": {"html": "https://wpnews.pro/news/reacttrace-in-solon-ai-turn-agent-runs-into-an-observable-boundary", "markdown": "https://wpnews.pro/news/reacttrace-in-solon-ai-turn-agent-runs-into-an-observable-boundary.md", "text": "https://wpnews.pro/news/reacttrace-in-solon-ai-turn-agent-runs-into-an-observable-boundary.txt", "jsonld": "https://wpnews.pro/news/reacttrace-in-solon-ai-turn-agent-runs-into-an-observable-boundary.jsonld"}}