Scalable Objects Persistence
When we started building the SOP AI Agent, the goal was never just to create another chatbot that summarizes text. The vision was far more ambitious: to create a conversational interface for high-scale data management.
We wanted an AI that could:
The ultimate goal was to have the AI act not just as a wrapper around a database, but as a programmable engine where a “Script” is effectively a stored procedure that can be invoked via a REST API.
We call this interface Natural Language Programming. The goal is to democratize software development by allowing typical users to author programs using plain English.
SOP functions as a compiler for this new language:
This shifts the role of the AI from a passive “assistant” to an active development platform, where the “code” is natural language and the “binary” is the JSON-based Script definition.
In our initial iteration, we fell into a common trap in AI development: anthropomorphism over structure. We designed the backend to “talk” like a human, streaming back logs of its thoughts mixed with data.
This led to Parsing Nightmares (frontend using Regex to hunt for JSON) and Scalability Bottlenecks (buffering massive responses in memory to validate them). We realized that to scale, we had to stop treating the AI as a chat partner and start treating it as a compute engine.
Perhaps the most difficult challenge was Recording vs. Runtime.
We initially tried to share state between the “User Session” and the “Script Runner”. This was a disaster. Scripts would accidentally commit user transactions, or user queries would bleed into script execution scopes. We needed a way to guarantee stability for the end-user recording session while maintaining a pristine environment for the runtime.
We completely refactored the engine around three core pillars: AST Composability, Session Isolation, and Structured Streaming.
We moved away from “script recording” (saving text commands) to an Abstract Syntax Tree (AST) approach. We defined a rigid schema for a ScriptStep:
type ScriptStep struct {
Name string // Unique identifier for the step
Description string // Human-readable explanation of what this step does
Type string // "command", "ask", "if", "loop", "script", "tool"
Command string // The actual instruction
Args map[string]any // Parameters
Steps []ScriptStep // Nested steps (for loops/conditionals)
}
This design unlocked Composability and Self-Documentation.
ScriptStep can be of type script, one script can call another.Description field allows the script to be read and understood by humans and LLMs alike, facilitating “Code Review” for AI agents.find_user, calculate_tax).process_payroll calls find_user then calculate_tax).runStepScript) simply pushes a new stack frame and executes the child script, just like a function call in a programming language.To solve the state management nightmare, we strictly separated the Recording Context from the Runtime Context.
scriptCtx). It gets its own variable scope and its own transaction boundaries.UserDB cannot accidentally touch SystemDB.Finally, to solve the “Chatty” trap and enable scaling, we implemented the JSON Streaming pattern, the heart & soul of SOP’s large data chunking extended to the AI space.
Instead of writing raw strings, the engine emits StepExecutionResult objects. We implemented a JSONStreamer that wraps the HTTP response writer.
type StepExecutionResult struct {
Type string `json:"type"` // "command", "ask", "error"
Result string `json:"result"` // The raw data payload
}
As soon as a step finishes, it is serialized and flushed.
The transformation is profound. Running a complex AI script now feels exactly like calling a standard REST API endpoint.
/play script=audit_salaryWe successfully bridged the gap between the flexibility of Generative AI and the rigidity required for data engineering. The SOP Agent is no longer just “chatting” about data; it is streaming it.