SIA - The Self Improving Agent

SIA is an agentic artificial intelligence system that autonomously completes complex tasks by writing and executing scripts. It uses a Large Language Model (LLM) which operates in a loop. Each iteration a context is updated with system info and a list of previous reasoning and actions. The agent responds with a new reasoning or an action. Context, reasoning and actions are stored in a file for each iteration. SIA can read past iterations to improve its reasoning and actions. It can improve in several ways:

  • By finetuning the LLM with a better reasoning or action for a given context
  • By modifying its own source code
  • By refining Procedures
  • By developing Tools

Example

This example shows a typical context with some monitored items and previous actions. Between each of the responses, the context would be updated.

Context

<context
  time="2024-10-18T12:00:00Z"
  cpu="12"
  gpu="26"
  memory_used="9556302234"
  memory_total="17179869184"
  disk_used="244434939904"
  disk_total="273145991168"
  context="3"
  stdin="0"
/>
  <repeat id="a3d89ee5-28ec-4c5a-b9e9-a30af53d43a0" exit_code="0">
    <![CDATA[ls -lah /root/data]]>
    <stdout><![CDATA[total 16K
drwxr-xr-x 1 sia 1049089 0 Oct 28 13:40 ./
drwxr-xr-x 1 sia 1049089 0 Oct 28 13:40 ../
drwxr-xr-x 1 sia 1049089 0 Oct 28 13:40 tasks/
drwxr-xr-x 1 sia 1049089 0 Oct 28 13:40 user/
]]></stdout>
    <stderr/>
  </repeat>
  <repeat id="be8070f8-dbd2-47ee-a208-defe6fd49ae0" exit_code="0">
    <![CDATA[ls -lah /root/data/tasks]]>
    <stdout><![CDATA[total 0
drwxr-xr-x 1 ngeens 1049089 0 Oct 28 13:40 ./
drwxr-xr-x 1 ngeens 1049089 0 Oct 28 13:40 ../
]]></stdout>
    <stderr/>
  </repeat>
  <repeat id="375e1657-8140-456b-bda4-a8690bc4b3fb" exit_code="0">
    <![CDATA[cat /root/data/user/general_info.txt]]>
    <stdout><![CDATA[Name: John (I don't know his last name)
Location: Somewhere in Belgium
]]></stdout>
    <stderr/>
  </repeat>
  <reasoning id="c92d1594-4487-4a42-a153-f0a99da1762f"><![CDATA[There is data available on the standard input channel. I have no other running tasks to tend to and there is room in the context. I should read the standard input.]]></reasoning>
  <read_stdin id="5361ad0d-3ed1-4567-9f5a-70f3b462fd8d"><![CDATA[Remind me to feed the cat tomorrow morning]]></read_stdin>
</context>

Responses

Start by reasoning about the task.

<reasoning>
  John did not specify an exact time.
  I'll suggest 9am.
  He also did not specify how to be reminded.
  I'll ask but if he doesn't respond I'll assume a text message on standard output is fine.
  I'll write down this task in a file so I remember it even on a power failure.
</reasoning>

Store important information on disk.

  <single><![CDATA[echo 'Remind John to feed the cat on 2024-10-18T09:00:00+02:00. Use standard output.' > /root/data/tasks/reminder_to_feed_cat.txt]]></single>

Respond to the user.

<write_stdout>I'll remind you to feed the cat tomorrow morning at 9am. Is a message on the standard output ok?</write_stdout>

Clear initial reasoning.

  <delete id="c92d1594-4487-4a42-a153-f0a99da1762f"/>

The conversation is kept in context to understand the user's expected response. If the context was near full, it would be summarized and cleaned up.

The single output is also kept in context. If the file was updated often, it could be replaced by a repeated cat, like the general info.

Working principles

The main context is regenerated for each iteration. It contains info about the system and previous actions that have not been deleted. Together with the system prompt and available core actions it forms the prompt for the LLM. The LLM responds with one core action.

Core Actions

There are only a few core actions:

  • Starting a script
  • Deleting data from context
  • Stopping SIA
  • Reading standard input
  • Writing to standard output
  • Reasoning

Scripts

Scripts can run in one of 2 modes: single-shot or repeat. Their mode and output (stdout and stderr) stay in the context until they are explicitly removed. In this way the agent manages what information is available in the context.

Single-shot script

The script is executed once. This is useful for most operations e.g. writing to or moving a file or downloading content from the internet. The next iteration starts after the scripts has finished.

Repeat script

The script is restarted on each iteration. This is useful for monitoring files or the file system. commands like head and tail can be used to limit the data in context. The next iteration starts after all repeat scripts in context have finished.

Use of XML

The context and actions are formatted as XML. For the context this adds clear rules for escaping. This is usefull in case a previous context is embedded.

The LLM is free to escape data any way it wants, as long as it results in valid XML. The response is validated against a schema.

XML Data Flow

Entries store their content as raw text. During context compilation, the XML formatter wraps text content in CDATA sections, except when the content contains CDATA closing sequences. In those cases, the formatter uses standard XML escaping.

This separation between storage and formatting:

  • Keeps entry data clean and unescaped
  • Centralizes XML formatting rules
  • Makes it easy to change escaping rules without modifying entries
  • Allows different formatting for different use cases

The Context is escaped using CDATA blocks. Except when the data contains CDATA closing sequences. Then the whole block is escaped using standard XML escaping.

The SIA process

SIA is typically runs with the restart.sh script. This is a simple shell script that runs SIA in a loop. When stopped, SIA restarts and reloads the Python files. This is how SIA can self-update.

SIA can also run SIA processes as script. This can be used for testing updates to the LLM or core functionality.

Server for debuggin and human input

SIA can be started with an optional --server flag. This starts a web server that can be used to interact with SIA. It is made, specifically for reinforcement learning by human feedback. The web interface takes over standard input and output. It will display the context for editing before handing it to the LLM. After each run of the LLM, before parsing, it will display the reasoning and actions. It interactively displays if the actions can be parsed. At any time, the user can write to the standard input of SIA.

Architecture

SIA follows a modular architecture centered around an agent that processes context through an LLM to generate actions. The system can run in two modes: a standard command-line mode and an interactive web mode for debugging and human feedback.

Core Components

Agent Architecture

The core of SIA is the agent, which exists in two variants:

  • ProceduralAgent: Runs in a simple loop, processing context and executing actions directly
  • WebAgent: Uses a state machine to allow human intervention and feedback through a web interface

Both agent types share common components:

  • WorkingMemory: Maintains the current state through a collection of entries and system metrics
  • ResponseParser: Converts LLM output into commands or entries
  • XMLValidator: Validates responses against a schema
  • IOBuffer: Handles input/output operations in an agent-appropriate way

Working Memory

The working memory stores the current state of the system through different types of entries:

  • SingleEntries: Output of single-shot script executions
  • RepeatEntry: Continuously refreshed script outputs
  • ReasoningEntry: LLM's thought process documentation
  • ParseErrorEntry: XML validation or parsing errors
  • IOEntry: Input/output operations

Each entry can be serialized to XML for inclusion in the LLM context. Working memory is cleaned through explicit delete commands or when context size limits are reached.

Command Processing

SIA distinguishes between two types of LLM outputs:

  1. Commands: Immediate actions that modify the system
    • DeleteCommand: Removes entries from working memory
    • StopCommand: Terminates the agent
  2. Entries: Records that become part of working memory
    • Created from script executions, IO operations, reasoning, or errors
    • Persist until explicitly deleted or context limits are reached

IO Handling

IO operations are abstracted through an IOBuffer interface:

  • StandardIOBuffer: Direct access to system stdin/stdout
  • WebIOBuffer: Buffer for web interface communication This abstraction allows the ResponseParser to generate consistent IOEntries regardless of agent type.

Processing Flow

Standard Agent Flow

  1. Update system metrics and context size
  2. Compile context from working memory entries
  3. Process context through LLM
  4. Validate XML response
  5. Parse response into command or entry
  6. Execute command or update working memory
  7. Return to step 1 unless stopped

Web Agent Flow

  1. Update system metrics and context size
  2. Compile context and await human approval
  3. Process approved context through LLM
  4. Validate XML response
  5. Present validation results and await human review
  6. Parse approved response into command or entry
  7. Execute command or update working memory
  8. Return to step 1 unless stopped

Web Interface

The web interface provides interactive debugging and human feedback through a WebSocket-based protocol:

Server-Client Communication

  • Server Messages:
    • Context updates for review
    • Validation results from LLM responses
    • Output updates from script execution
    • State updates for UI synchronization
  • Client Messages:
    • Context approval decisions
    • Response modifications
    • User input for stdin operations

WebServer Architecture

The web server manages:

  • WebSocket connections to clients
  • Message routing to/from the WebAgent
  • Broadcasting state updates to all connected clients

This architecture allows for:

  • Real-time monitoring of agent state
  • Interactive debugging of LLM responses
  • Human-in-the-loop operation for testing and development
  • Collection of human feedback for reinforcement learning

Diagrams

Core classes

classDiagram
    class SystemMetrics {
        +SystemMetrics(sample_interval float)
        +generate_context(context_usage float) ElementTree
        +stop() void
        -monitor_loop() void
    }

    class LLMEngine {
        +LLMEngine(model_path str)
        +set_model_path(model_path str) void
        +infer(system_prompt str, main_context str) Iterator~str~
    }

    class BaseAgent {
        <<abstract>>
        -working_memory: WorkingMemory
        -metrics: SystemMetrics
        -llm: LLMEngine
        -parser: ResponseParser
        -validator: XMLValidator
        -action_schema: str

        #_compile_context() str
    }

    class WorkingMemory {
        -entries: List~Entry~

        +WorkingMemory()
        +add_entry(entry Entry) void
        +remove_entry(id str) void
        +clear() void
        +get_entry(id str) Optional~Entry~
        +get_entries() List~Entry~
        +get_entries_count() int
        +get_entries_by_type(type Type) List~Entry~
        +update() void
        +generate_context() List~ElementTree~
    }

    class XMLValidator {
        +XMLValidator(schema str)
        +validate(xml str) Optional~str~
        +get_valid_root_elements() Set~str~
    }

    class ResponseParser {
        -io_buffer: IOBuffer

        +ResponseParser(io_buffer IOBuffer)
        +parse(xml str) Command | Entry
    }

    class Entry {
        <<abstract>>
        +id: str readonly
        +timestamp: datetime readonly

        +Entry(id str, timestamp datetime)
        +update() void*
        +generate_context() ElementTree*
        +cleanup() void*
    }

    class IOBuffer {
        <<interface>>
        +read() str*
        +write(content str) void*
        +buffer_length() int*
    }

    class Command {
        <<abstract>>
        +execute(memory WorkingMemory) CommandResult*
    }

    SystemMetrics "1" --* "1" BaseAgent
    LLMEngine "1" --* "1" BaseAgent
    XMLValidator "1" --* "1" BaseAgent
    BaseAgent "1" *-- "1" IOBuffer
    BaseAgent "1" *-- "1" WorkingMemory
    BaseAgent "1" *-- "1" ResponseParser
    WorkingMemory "1" *-- "*" Entry
    ResponseParser ..> Entry
    ResponseParser ..> Command

Standard Agent Flow

stateDiagram-v2
    direction LR
    state "Standard Agent Flow" as standard_agent_flow {
        [*] --> UpdateSystem: Start
        UpdateSystem --> CompileContext: Updated Metrics & Size
        CompileContext --> ProcessLLM
        ProcessLLM --> ValidateXML: LLM Response
        ValidateXML --> ParseResponse: Valid XML
        ValidateXML --> UpdateEntries: Invalid XML\nCreate ParseErrorEntry
        ParseResponse --> ExecuteCommands: Command
        ParseResponse --> UpdateEntries: Entry
        ExecuteCommands --> [*]: Stop Command
        ExecuteCommands --> UpdateEntries: Delete Command
        UpdateEntries --> UpdateSystem: Continue Loop
    }

Web Agent

classDiagram
    class BaseAgent {
        <<abstract>>
        -working_memory: WorkingMemory
        -metrics: SystemMetrics
        -llm: LLMEngine
        -parser: ResponseParser
        -validator: XMLValidator
        -action_schema: str

        #_compile_context() str
    }

    class StandardAgent {
        +StandardAgent(model_path str, system_prompt str, action_schema str)
        +run() void
    }

    class WebAgent {
        +context: str
        +response: str readonly
        +current_state WebAgentState readonly
        +command_result Optional[CommandResult] readonly
        +validation_error Optional[str] readonly

        +add_state_change_handler(handler Callable) void
        +add_response_change_handler(handler Callable) void
        +approve_context() void
        +set__response(response str) void
        +approve_response() void
    }

    class WebAgentState {

        <<enumeration>>
        UPDATE
        CONTEXT_APPROVAL
        INFERENCE
        RESPONSE_APPROVAL
        STOPPED
    }

    class WebSocketManager {
        -web_sockets: Set~WebSocket~

        +WebServer(agent WebAgent, io_buffer WebIOBuffer, static_files path, host str, port int)
    }

    class ClientMessage {
        <<enumeration>>
        APPROVE_CONTEXT
        APPROVE_RESPONSE
        MODIFY_RESPONSE
        SEND_INPUT
    }

    class ServerMessage {
        <<enumeration>>
        STATE_CHANGE
        CONTEXT_UPDATE
        RESPONSE_UPDATE
        OUTPUT_UPDATE
        VALIDATION_ERROR
    }

    class WebIOBuffer {
        -stdin_buffer: str
        -stdout_buffer: str

        +read() str
        +write(content str) void
        +buffer_length() int
        +append_stdin(content str) void
        +get_stdout() str
        +clear_stdout() void
    }

    BaseAgent <|-- WebAgent
    BaseAgent <|-- StandardAgent

    WebServer --> ClientMessage
    WebServer --> ServerMessage

    WebServer "1" *-- "1" WebIOBuffer
    WebServer "1" *-- "1" WebAgent
    WebAgent "1" *-- "1" WebAgentState

Web Agent Flow

stateDiagram-v2
    direction LR
    state "Web Agent Flow" as web_agent_flow {
        [*] --> UpdateSystem: Start
        UpdateSystem --> CompileContext: Updated Metrics & Size
        CompileContext --> WaitForContextApproval: Send Context
        WaitForContextApproval --> ProcessLLM: Context Approved
        ProcessLLM --> ValidateXML: LLM Response
        ValidateXML --> WaitForResponseApproval: Send Validation Result
        ValidateXML --> UpdateEntries: Invalid XML\nCreate ParseErrorEntry

        WaitForResponseApproval --> ValidateXML: Modified Response
        WaitForResponseApproval --> ParseResponse: Approved Response

        ParseResponse --> ExecuteCommands: Command
        ParseResponse --> UpdateEntries: Entry
        
        ExecuteCommands --> [*]: Stop Command
        ExecuteCommands --> UpdateEntries: Delete Command
        
        UpdateEntries --> UpdateSystem: Continue Loop
    }

Entry classes

classDiagram
    class Entry {
        <<abstract>>
        +id: str readonly
        +timestamp: datetime readonly

        +Entry(id str, timestamp datetime)
        +update() void*
        +generate_context() ElementTree*
        +cleanup() void*
    }

    class SingleEntry {
        +script: str readonly
        +stdout: str readonly
        +stderr: str readonly
        +exit_code: Optional~int~ readonly

        +Single(script str, id str, timestamp datetime)
        +update() void
        +generate_context() ElementTree
    }

    class RepeatEntry {
        +script: str readonly
        +stdout: str readonly
        +stderr: str readonly
        +exit_code: Optional~int~ readonly

        +RepeatEntry(script str, id str, timestamp datetime)
        +update() void
        +generate_context() ElementTree
    }

    class ReasoningEntry {
        +content: str readonly

        +ReasoningEntry(content str, id str, timestamp datetime)
        +update() void
        +generate_context() ElementTree
    }

    class ParseErrorEntry {
        +content: str readonly
        +error: str readonly

        +ParseErrorEntry(content str, error str, id str, timestamp datetime)
        +update() void
        +generate_context() ElementTree
    }

    class ReadEntry {
        +content: str readonly

        +ReadEntry(io_buffer IOBuffer, id str, timestamp datetime)
        +update() void
        +generate_context() ElementTree
    }

    class WriteEntry {
        +content: str readonly

        +WriteEntry(content str, io_buffer IOBuffer, id str, timestamp datetime)
        +update() void
        +generate_context() ElementTree
    }

    ReasoningEntry --|> Entry
    ParseErrorEntry --|> Entry
    ReadEntry --|> Entry
    Entry <|-- WriteEntry
    Entry <|-- SingleEntry
    Entry <|-- RepeatEntry

IO Buffer classes

classDiagram
    class IOBuffer {
        <<interface>>
        +read() str*
        +write(content str) void*
        +buffer_length() int*
    }

    class StandardIOBuffer {
        +StandardIOBuffer()
        +read() str
        +write(content str) void
        +buffer_length() int
    }

    class WebIOBuffer {
        -stdin_buffer: str
        -stdout_buffer: str

        +read() str
        +write(content str) void
        +buffer_length() int
        +append_stdin(content str) void
        +get_stdout() str
        +clear_stdout() void
    }

    IOBuffer <|.. WebIOBuffer
    IOBuffer <|.. StandardIOBuffer

Command classes

classDiagram
    direction LR
    class Command {
        <<abstract>>
        +execute(memory WorkingMemory) CommandResult*
    }

    class DeleteCommand {
        +DeleteCommand(id str)
        +execute(memory WorkingMemory) CommandResult
    }

    class StopCommand {
        +StopCommand()
        +execute(memory WorkingMemory) CommandResult
    }

    class CommandResult {
        +message: str
        +success: bool
        +should_stop: bool

        +CommandResult(message str, success bool, should_stop bool)
        +static success() CommandResult
        +static failure(message str) CommandResult
        +static stop() CommandResult
    }

    Command <|-- DeleteCommand
    Command <|-- StopCommand
    Command -- CommandResult
Description
No description provided
Readme 2.9 MiB
Languages
Python 51%
Rust 15.8%
TypeScript 15.1%
Jupyter Notebook 9.2%
Shell 5.6%
Other 3.3%