@Cyanisok

Few More Steps from LLM - The Agentic AI Paradiagm

LLMs have learned to answer, now they need to act. This blog contains notes I took alongside my learning progress on agentic AI.

Two Paradiagm Ahead: Static Workflow OR Agentic Orchestration?

SOTA agent frameworks worthy of reference and research today:

Prerequisite terms:

  • Agent SDK: Provides building blocks for agent programming
  • Graph Runtime: Manages complex state transitions
  • Harness: Assembles agent loops, tools and interactions into runnable products
  • Platform: Further delivers evaluation, observability and deployment capabilities

One‑sentence project positioning:

  • Pi: A minimal coding agent harness ready for direct use and extension.
  • tRPC‑Agent‑Go: Full‑stack agent SDK and runtime for Go backend services.
  • Spring AI Alibaba: Agent, graph and platform suite for Spring‑based Java enterprise applications.
  • AgentScope: Python multi‑agent platform covering development, collaboration, evaluation and training.
  • LangGraph/langgraph4j: Low‑level state‑graph runtime supporting persistence, suspend‑resume and cycles.
  • AG2: Multi‑agent framework centered on communication protocols and collaboration networks.
  • Pydantic AI: Python agent SDK emphasizing type safety, structured output and backend engineering experience.

Before Entering AgentRunner.run_and_capture()

This section is AI-generated.

The Agent is not exposed as a generic agent.run RPC. It starts only after a training process fails and the Incident workflow creates a fixed Agent profile for that failure.

The overall flow looks like this:

TUI / VS Code
    │ TCP + NDJSON
    ▼
SocketServer
    │ JSON-RPC routing
    ▼
CoreApp
    ├── launch.start → JobSupervisor → training process
    └── process failure → IncidentCoordinator
                                  │
                                  ▼
                            IncidentRuntime
                                  │
                                  ▼
                      AgentRunner.run_and_capture()

Communication Between the Client and Core

The client uses SocketClient to connect to the local cyan-core daemon. The transport is TCP. Messages are newline-delimited JSON objects, so each line represents one message.

A client will typically connect, start its event loop, and subscribe to the events it needs:

SocketClient.connect()
  -> asyncio.open_connection(host, port)

client.on_event(handler)
create_task(client.run_event_loop())

send_command("event.subscribe", {...})

Training launch uses launch.preview and launch.start. The preview command parses the training command and creates a fingerprint. The start command launches the process only if that fingerprint is still valid.

An Incident follow-up uses the explicit incident.follow_up command:

send_command("incident.follow_up", {
  "incident_id": "...",
  "content": "Please investigate the evidence related to device memory again."
})

On the Core side, the request follows this path:

SocketServer._handle_line()
  -> json.loads(line)
  -> JsonRpcRequest.model_validate(raw)
  -> find the registered handler
  -> call the corresponding CoreApp method
  -> return JsonRpcSuccess

Several layers are involved here:

  • TCP provides the byte transport.
  • NDJSON frames messages by newline.
  • JSON-RPC 2.0 defines the request, response, and error structure.
  • EventBus provides in-process asynchronous event dispatch. It is separate from the network transport.

Creating an Agent Run After a Training Failure

JobSupervisor owns the training process. When the process exits with a non-zero status, the supervisor passes the failure information and the persisted stdout/stderr logs to IncidentCoordinator.

The coordinator then:

  1. Builds a Failure Capsule from the Attempt logs.
  2. Creates or recovers an Incident.
  3. Writes the current run.json before starting the background task.
  4. Generates a run_id and starts IncidentRuntime.
  5. Verifies the log hashes, scans the logs, and selects bounded evidence.

The complete logs are not placed directly into the model context. The evidence selector chooses limited log ranges and records their sources, byte ranges, and hashes. build_incident_profile() uses this selection to construct the Agent profile for the current run.

An Incident follow-up uses the same Runtime. It creates a new run_id and keeps a bounded summary of the previous diagnosis, proposal, or smoke result so the next run has limited continuity without loading an old conversation.

How IncidentRuntime Builds the Agent

Only after the evidence has been prepared does IncidentRuntime create an AgentRunner:

IncidentRuntime.run()
  -> build_failure_capsule()
  -> select_evidence()
  -> build_incident_profile()
  -> AgentRunner.run_and_capture()

build_incident_profile() registers six tools for the run:

read_file
list_dir
search_text
read_job_log
submit_diagnosis
propose_patch

The first four tools only read the workspace or training logs. submit_diagnosis writes a structured diagnosis to the Incident artifacts. propose_patch generates a patch from one exact and unique SEARCH/REPLACE operation.

The Agent does not modify the workspace directly and cannot execute shell commands.

The profile also applies several limits:

  • A maximum of 12 steps.
  • A 128 KiB input-context limit.
  • A separate limit for the initial log evidence.
  • One fixed system prompt for the current Incident.
  • No old session, global memory, or generic Agent extensions.

AgentRunner.run_and_capture()

AgentRunner assembles the execution environment for one run. It does not decide the business state of the Incident.

It first creates the run directory and a local EventBus. It then creates:

  • ExecutionContext, which stores the current instruction, message history, step count, and context size;
  • ToolRegistry, which contains the tools allowed by the current profile;
  • the LLM provider, using the configured model provider;
  • AgentLoop, which performs the model and tool execution loop.

Each run starts with the current instruction:

user: the current Incident investigation instruction
assistant: model response and tool_use blocks
user: corresponding tool_result blocks

The user role for a tool result means that the result is being returned to the model. It does not represent another message typed by the human user.

The AgentLoop Execution

AgentLoop repeats the following sequence:

Check the context size
    ↓
Call the LLM provider
    ↓
Record the assistant text and tool requests
    ↓
If the model requested tools, invoke them
    ↓
Add the tool results to the context
    ↓
Continue or finish the run

When the model returns tool_use, the Agent invokes the registered tools and adds their results as tool-result messages. Tool failures are also returned as structured errors, allowing the model to decide whether it can continue investigating.

When the model returns end_turn, the run completes successfully. The run can also finish because it reaches the step limit, exceeds the context budget, encounters an LLM failure, or is cancelled.

At the end, AgentRunner publishes a RunFinishedEvent and returns a RunOutcome. The outcome contains bounded metadata such as the status, failure reason, step count, and context sizes. It does not contain duplicated training logs or unrestricted tool output.

Returning Events and Persisted Results to the Client

Events generated during a run follow two paths:

AgentLoop / LLM provider / tool invocation
    └── local EventBus
          ├── EventWriter → incidents/.../runs/<run_id>/events.jsonl
          └── Core EventBus
                ├── trace handler
                └── IpcEventBroadcaster
                      └── TCP client

events.jsonl stores the event stream for the current run. On the Core side, IpcEventBroadcaster sends events that match the client's subscription.

The wire format for a pushed event is:

{
"kind": "event",
"event": {
"type": "step.started",
"run_id": "run-...",
"step": 1
}
}

After receiving an event, the client can call job.get to retrieve the latest Job, Incident, diagnosis, and proposal state.

When the Agent run finishes, IncidentRuntime updates run.json. IncidentCoordinator then checks whether a diagnosis and proposal were produced and advances the Incident state machine accordingly.

The responsibilities are therefore separated:

  • The client sends commands and displays state.
  • service owns TCP, NDJSON, and JSON-RPC communication.
  • training.incidents owns the Incident lifecycle.
  • agent owns the bounded model-and-tool execution loop.

Recursive Language Model (RLM) & Agentic Reinforcement Learning (ARL)

"A Minimalist Design" - RLM

"The only tool that a language model should have access to is a coding tool, and all other tools should be embedded inside its coding environment."

The architecture of RLM is simple. For each depth of its recursion, there are mainly two things: the LM (for inference, generating sub-recursion prompts and summarizing answers) & the Environment (e.g. REPL, for executing the queries from LM, accessing external resources and gathering results of sub-tasks).

"The key insight is that long prompts should not be fed into the neural network directly but should instead be treated as part of the environment that the LLM can symbolically interact with and launch LLM calls inside of."

Another opinion is that RLM is the direct marriage of two important axes of model capability - reasoning and tool use. It consists of recursive model calls like a multi-agent system but more inherently stays on the level of language model itself. Remind that "recursive model calls" means their context windows can be chained together to form a longer reasoning process - maybe it really works well somehow..

ARL is a Post-Training Paradiagm of Agents

Normally it outputs the optimized LLM policy which is likely certain parameters of the models have been changed.

Environments - the training substrate includes:

  • Agent (LLM policy)
  • Action Executor (e.g. sandbox, browser, shell, code runner...)
  • World State (e.g. database, file system, broswer DOM...)
  • Observer (state -> tokens)
  • Reward (verifier to judge)

A rollout is a complete behavioural trajectory of an agent as it carries out a task within the environment; a reward is the training score assigned by the system to the final outcome of that trajectory or the quality of its intermediate behaviours. Agentic RL enables LLM agents to learn to complete multi-step tasks more reliably through numerous cycles of:

rollout -> reward -> policy update

There are various training frameworks, but normally are all decoupled into basic modules like verifiers, generators and trainers through roughly four processes: generation, scoring, batching, weight update.

The common paradiagm is to make things asynchronous as possible that is to decouple the generation from the training - anything synchronous leaves GPUs idle for most of the run.

Blog