Skip to content

Architecture ​

OryxOS is a Spring Boot 3 monolith running on JDK 21. It calls LLMs through Spring AI Alibaba, implements its own ReAct loop as the Agent core, and ships as a single executable JAR.

The stack in one line: JDK 21 + Spring Boot 3 + Spring AI Alibaba + home-grown ReAct + SQLite + Picocli.

OryxOS architecture

Layers ​

LayerComponentsResponsibility
EntryCLI Channel, Web Service (REST), AgentSchedulerMessages in and out. CLI and REST are human-triggered; the scheduler is clock-triggered
Unified entry pointAgentServiceThe single orchestrator shared by all three entries, agnostic to the source
EngineReActLoop, PromptBuilder, ToolExecutorThe Agent's brain
CapabilitiesProvider, Memory, ToolLLM calls, context and execution for the engine
FoundationAgentLoader, ContextLoader, ConfigLoader, SQLite, file systemAgent definitions, configuration and secrets, persistence

In one sentence: Provider, Memory and Tool feed the ReAct engine, and the engine is exposed through three entries — CLI, Web Service and the scheduler.

Everything runs in one process. External dependencies — LLM vendor APIs, external MCP servers, enterprise IM — sit outside the application boundary, and every crossing is sandbox-checked and audited.

How a message is processed ​

text
A message arrives from the CLI / REST API / scheduler
  → AgentService.process(Session, message)
    → PromptBuilder assembles the prompt
       (AGENT.md body + bootstrap files + Skill metadata + current time + long-term memory + history + tools)
    → ProviderService calls the LLM                                  ── writes llm_calls
       ├─ no tool call → return the final answer
       └─ tool call → ToolExecutor: look up → sandbox check → run    ── writes tool_invocations
                      → append the result to history → assemble the prompt again
    → stop at the iteration limit (default 10)
  → persist the session

Core capabilities ​

LLM access ​

ProviderService manages all providers and hides vendor differences from the ReAct loop. With several providers side by side, it keeps an explicit provider name → ChatModel map instead of guessing from bean types. Every call records token usage, provider and model in llm_calls.

ReAct loop ​

ReActLoop is the most important code in OryxOS. It is implemented in-house rather than on Spring AI's Agent abstractions. Spring AI does exactly two things here: provider protocol conversion and JSON Schema generation for @Tool. Its automatic tool execution is disabled; ToolExecutor alone schedules tools, so no tool ever runs twice.

Memory ​

MemoryService is the single facade over all memory layers; the ReAct loop only talks to it:

  • Session memory: delegated to SessionManager, persisted in SQLite and restored after restarts; early turns are truncated when too long
  • Long-term memory: delegated to LongTermMemoryStore, by default .oryxos/memory/MEMORY.md with a core and an archival section. The core section is never truncated; truncation and search apply to the archive only. It is re-read on every turn with no cache, so new memories are visible immediately
  • Episodic memory: coming in the extension phase

Agents write and search memory with the built-in save_memory and recall_memory tools.

Tools ​

Every tool — built-in, MCP or @Tool Bean — is wrapped as a uniform OryxTool, so the loop never cares where a tool comes from:

TierEffortHow
Zero code (recommended)LowestWrite an Agent directory and reuse MCP servers via mcp_servers.yaml
Light codeMediumWrite an MCP server in any language; OryxOS connects as an MCP client
Full codeHighestWrite a Java Spring Bean with @Tool, called in-process

AGENT.md and Skills are not tools. The Agent body is injected into the system prompt by ContextLoader. Bound Skills contribute only their name, description and path each turn; the model reads a Skill's body with read_file when it needs it (progressive disclosure).

Web Service ​

The Web Service is OryxOS's front door. The core phase ships 10 REST endpoints: 4 for sessions, 1 for Agent invocation, 3 for Agent / memory / tool queries and 2 for health and info. Limits: 32 KB per message, at most 100 history entries per response, a 60-second timeout per invocation.

Security: sandbox and audit ​

Sandbox ​

The sandbox is interface-first: a neutral Sandbox.enforce(action) interface with one implementation in the core phase — application-level allow-lists:

ActionCheck
File read/writeNormalized path matched against the allow-list; blocks ../ traversal
Shell commandOnly allow-listed executables; argument arrays passed directly, never through a shell
HTTP requestHost matched against the domain allow-list
SMTPExact host:port allow-list

Later phases add container and microVM isolation — the interface stays the same; only new implementations are added.

WARNING

Application-level allow-lists stop a model's mistakes, not a determined attacker. Do not run fully untrusted code or offer multi-tenant service on the core-phase sandbox.

Audit ​

Every LLM call is written to llm_calls; every tool call — including failures and sandbox rejections — to tool_invocations. Audit data is stored in the database from day one, not reconstructed from logs later.

Key technical decisions ​

#DecisionChoiceWhy
1ReAct implementationHome-grownFull control; room to customize the loop
2Spring AI boundaryProtocol conversion + schema generation only; auto tool execution disabledNo double tool calls; OryxOS owns the loop
3Execution modelSynchronous + Java 21 virtual threadsPlain code, high concurrency on one node
4Tool registration@Tool + OryxTool abstractionOne interface for built-in and MCP tools
5HTTP layerSpring MVC + virtual threadsStraightforward; streaming later via SseEmitter
6SandboxInterface-first + application allow-listsSecurityManager is gone in JDK 21; swapping implementations never touches callers
7PersistenceSQLite + Spring Data JPA + MEMORY.mdSingle-binary deployment; audit tables written from day one

Persistence ​

DataStoreNotes
SessionsSQLite sessionsHistory serialized as JSON; survives restarts
AuditSQLite tool_invocations, llm_callsOne row per call
SchedulesSQLite scheduled_tasks, task_executionsTask state and run history
Notification channelsSQLite notify_channelsGlobal registry referenced by name
Agent definitions, bootstrap files, long-term memory, MCP configFile system .oryxos/Editable, git-trackable, easy to back up

Modules ​

OryxOS is a Maven multi-module project with 9 modules in the core phase:

ModuleResponsibility
oryxos-coreCore abstractions and engine: ReActLoop, PromptBuilder, ToolExecutor, AgentService, AgentLoader, ContextLoader, AgentScheduler
oryxos-providerLLM provider abstraction with explicit name mapping
oryxos-memoryMemoryService facade, long-term memory store, memory tools
oryxos-toolBuilt-in tools, MCP client, ToolRegistry, sandbox, notifications
oryxos-channel-cliCLI chat channel
oryxos-webREST API, global error handling, OpenAPI docs
oryxos-storageSQLite persistence
oryxos-cliPicocli entry point, configuration and secret loading
oryxos-bootSpring Boot bootstrap and aggregation

Modules are decoupled through interfaces: oryxos-core depends on no other module, and implementations depend on interfaces declared in core. New channels or tools are added as new modules without touching the engine.