Status: implemented. The migration completed through small, tested commits. Legacy import paths remain as compatibility/composition facades; capability behaviour now lives in focused classes and repositories.
This document records the agreed target architecture for the OpenAI4S backend refactor. It is a compatibility-preserving reorganization, not a reduction of the product to CoreCoder's function-calling loop.
OpenAI4S remains a hybrid agent with two deliberately separate action planes:
Native JSON tool calling is the control plane. It is limited to
orchestration, permission checks, external services, and operations that
require human approval. Providers must expose these calls through their
native structured tool-call protocol; fenced ```tool JSON is not the
target representation.
Python/R Code-as-Action is the scientific execution plane. Data loading, transformation, numerical analysis, simulation, plotting, model execution, and other scientific computation continue to run as real code in persistent Python or R kernels.
The Python kernel retains synchronous mid-cell host.* RPC. A cell may use
host services while its scientific program is running; native JSON tool
calls do not replace this inner loop.
host.submit_output(...), called from a Python cell, is the only task
completion signal. Plain assistant text, an R cell, a native tool result,
an empty action, or exhaustion of work does not mean success. Maximum turns,
cancellation, refusal, timeout, and failure are non-success stop reasons.
All current capabilities are retained, including persistent Python/R kernels, delegation, skills, MCP, web access, compute/folding, environment management, artifacts and lineage, replay/review, security controls, permissions, the CLI, the HTTP/WebSocket server, and session persistence.
Public contracts remain compatible. This includes documented Python APIs,
host.*, CLI behavior and environment variables, REST and WebSocket
payloads, persisted sessions and database data, and user-visible behavior.
Internal module paths and private implementation details may change. Public
imports that move must keep compatibility re-exports for the supported
deprecation period.
The refactor is delivered as a sequence of reviewable commits. Each phase must preserve a runnable system and pass the relevant offline and end-to-end checks before the next phase starts.
The outer loop consumes one normalized assistant reply and chooses one action kind:
assistant reply
|
+-- native JSON tool call(s) --> control-plane executor
| orchestration / permission / external
| service / human approval
|
+-- one Python or R cell ------> scientific executor
| persistent language kernel
|
+-- no valid action -----------> corrective observation
Provider-specific streaming formats are normalized before they reach the agent loop. Native tool-call IDs and their results remain paired as one indivisible protocol group. Likewise, a code action and its observation remain an indivisible turn group for interruption handling and context compaction.
The control plane and scientific plane share policy and observability, but not responsibility:
host.* calls route to the same capability
registry and service implementations so authorization and auditing cannot
drift.submit_output is intentionally absent from the native JSON tool registry.
It remains an in-kernel host capability available only to the Python control
channel.Human approval is represented as a durable control-plane state. An operation may pause with a pending approval and later resume the same session; approval must not be inferred from model text. A live approval wakes the exact blocked call. A daemon restart destroys that call stack, so a later approval never replays persisted arguments: it records the interrupted action as unexecuted and requires a fresh, explicit continuation/replan turn.
There will be one reusable AgentEngine for in-process, CLI, WebSocket, root,
and delegated runs. Server and CLI layers adapt events and persistence; they do
not implement their own model/action/execution loop.
The engine depends on small internal protocols:
ModelClient messages -> normalized reply + native tool calls + usage
ActionRouter normalized reply -> CodeAction | NativeToolBatch | NoAction
ContextPolicy complete turn groups -> retained/compacted context
ActionExecutor action -> observation + optional submitted completion
EventSink typed run events -> CLI / WebSocket / persistence adapters
Cancellation reports whether the current run should stop
AgentEngine must not construct or import a concrete store, gateway, kernel,
dispatcher, skill loader, replay recorder, or WebSocket hub. Those dependencies
are assembled at the application boundary. The loop remains small enough that
its state transitions and terminal conditions can be reviewed in one place.
The finished layout is intentionally compact. A directory exists only when it owns a real dependency boundary. Large public modules remain as compatible composition facades, while the behaviour they used to contain is grouped by domain below.
openai4s/
agent/
engine.py sole outer-loop state machine
actions.py native-tool / one-cell / no-action routing
runtime.py local and CLI adapter
control.py protected native-tool execution
models.py · ports.py provider-neutral values and dependency ports
llm/
client.py provider-neutral chat orchestration
messages.py · models.py normalized messages, replies, calls, usage
tooling.py · transport.py wire assembly and stdlib transport
llm/__init__.py compatible public facade
execution/
models.py cell/capture value types
watchdog.py timeout and exact-kernel recovery policy
tools/
base.py executable Tool interface and safe defaults
contexts.py narrow workspace/environment ports
<capability>.py one named class with schema, policy, behaviour
registry.py ordered class catalogue and runtime instances
native.py provider-neutral JSON declarations
host_dispatch.py permission/audit/replay/injection routing facade
host/
files.py workspace boundary service
llm.py · completion.py model fanout and sole completion state
data.py · delegation.py query/artifact/lineage and child orchestration
remote_capabilities.py verified service registration
remote_science.py real fold/mutation remote execution
progress.py · skills.py plan/todo and skill services
endpoints.py · mcp.py managed endpoints and MCP services
credentials.py in-memory credential service
sdk/
host.py compatible in-kernel host facade
compute.py host.compute namespace and job handles
kernel/
manager.py worker lifecycle and synchronous host RPC
supervisor.py per-session Python/R slot ownership
worker.py persistent Python worker
r_kernel.py · r_worker.R persistent analysis-only R channel
background.py · environments.py · guards.py · provenance.py
store.py schema/migration/connection + compatible facade
storage/
frames.py projects, frames, messages, steps, cell log
artifacts.py versions, snapshots, lineage
metadata.py notes, folders, endpoints, compaction, audit
agents.py · connectors.py · memories.py
annotations.py · plans.py · permissions.py · settings.py
server/
gateway.py stdlib HTTP/WS composition and route adapter
agent_run.py AgentEngine-to-Web event/action adapter
cell_run.py one scientific cell transaction
artifacts.py capture, versions, restore, mutations
plans.py · reviews.py plan and evidence-review orchestration
skills.py · titles.py customization and background titles
execution_views.py execution-log/lineage presentation
daemon.py · webui/
cli/main.py CLI adapter
security/ · compute/ policy and remote-compute subsystems
These ownership and dependency boundaries are architectural constraints.
agent.engine imports only agent action/context/event abstractions and the
injected protocols. It never imports server, storage, or concrete kernel
and host implementations.llm owns provider wire differences. No provider-specific response object
crosses into the agent engine.agent.runtime and server.agent_run adapt engine actions to the local or
Web runtime; shared execution values/watchdog policy live in execution.host_dispatch.HostDispatcher is the policy-aware router. Capability
business logic lives in classes under host/ or the corresponding Tool
class; native tools and kernel RPC cross the same envelope.server/ own durable run operations. gateway.py and the
CLI are adapters/composition roots and cannot contain another agent loop.Store owns the connection, schema, and migrations. Domain repositories
share its connection and lock; they do not open competing write connections.sdk and security guards, never
server or storage code. The host process continues to execute no shell on
behalf of the model.Before moving behavior, the current contracts must be inventoried and protected with characterization tests. At minimum the gates cover:
host.submit_output as the sole successful completion path;Compatibility shims may forward old public imports or call signatures to new implementations. They must not duplicate business logic.
Each phase is a separate commit or short commit series with an explicit test gate. Later phases do not begin while the preceding compatibility gate is red.
Current implementation status:
openai4s.llm;openai4s.agent.engine.AgentEngine is the only outer-loop state machine;tool remains a silent compatibility parser, while new runs expose
provider-native JSON schemas;HostDispatcher delegates workspace, LLM, completion, data/lineage,
delegation, progress, credentials, MCP, endpoints, remote capabilities, and
real remote-science work to focused services;Store owns one connection/migration boundary and forwards domain work to
frame, artifact, metadata, settings, permission, plan, annotation, agent,
connector, and memory repositories;sdk.host retains its import surface while the compute namespace lives in
sdk.compute; all extractions are covered by direct contracts and the
existing offline suite.AgentEngine```tool transport after provider and UI parity is proven;
keep only a temporary compatibility parser if required for saved context.host_dispatch.py, sdk/host.py, store.py, and gateway session logic
surgically behind compatibility facades.Phases 1–6 are complete. The remaining large facade files are deliberate:
gateway.py contains the framework-free route table/composition plus demo
payloads, store.py contains the SQLite schema/migrations and forwarding API,
and host_dispatch.py contains the shared policy envelope and compatibility
adapters. Domain algorithms no longer need to be added directly to those
facades.
host.submit_output completion contract.