openai-agents-submilli
Submilli is a dedicated secure runtime for code-mode agents, with built-in isolation, governance, and permissions.
openai-agents-submilli is the Python integration for the
OpenAI Agents SDK. It lets your agent execute
TypeScript code in Submilli's runtime, and lets that code call your existing function tools
under the Blueprint's policy and the SDK's own approvals.
Install
Python 3.11 or later and submilli-server 0.4.1 or later are required.
pip install openai-agents-submilli
Follow Connect OpenAI Agents for a complete setup with a server, Blueprint, model provider, agent, and session cleanup.
Use it
from agents import Agent, Runner
from openai_agents_submilli import SubmilliTools
# Server and token come from SUBMILLI_SERVER and SUBMILLI_SERVER_TOKEN.
submilli = SubmilliTools(blueprint="research")
# Built once and shared: the user and the conversation come with each run.
agent = Agent(name="researcher", instructions="...", tools=submilli.tools)
result = await Runner.run(
agent,
"What is new in the latest Rust release?",
context={"user_id": "u_ada", "conversation_id": "c_1"},
)
# When the conversation is over:
await submilli.aclose_conversation("c_1")
Toolkit reference
The tools are async. Runner.run, Runner.run_streamed, and Runner.run_sync all work.
SubmilliTools(
blueprint="research",
server=None,
token=None,
variables=None,
secrets=None,
conversation_id=None,
store=None,
client=None,
ptc=None,
)
| Setting | Contract |
|---|---|
blueprint |
Name of a Blueprint already registered on the server; used for every program |
server |
submilli-server URL; defaults to $SUBMILLI_SERVER, then http://127.0.0.1:8128 |
token |
Server token; defaults to $SUBMILLI_SERVER_TOKEN |
variables |
Fixed dict[str, str], or a function from the run context to one; defaults to mapping context user_id to Blueprint variable userId |
secrets |
Fixed dict[str, str], or a function from the run context to one, for the Blueprint's harness secrets |
conversation_id |
Function from the run context to the conversation's id; defaults to reading context conversation_id |
store |
Where each conversation's session id is kept: InMemorySessionStore (default), SQLiteSessionStore, or your own SessionStore |
client |
Existing submilli.AsyncSubmilli; replaces server and token |
ptc |
Application tools that programs may call through the @tools Package |
The run context is the context you pass to Runner.run. It may be a dict, a dataclass, or a
Pydantic model; the defaults read user_id and conversation_id as keys or attributes. The
model never sees it.
submilli.tools holds the six tools below. Before the first model call, the toolkit fetches the
instructions for using them from the Submilli server.
| Tool | Does |
|---|---|
submilli__typescript__execute |
Runs a program |
submilli__typescript__last_run |
Returns the conversation's last run and full console |
submilli__typescript__packages__search |
Searches Packages available to the program |
submilli__typescript__packages__docs |
Returns a Package's documentation and declarations |
submilli__typescript__builtins__list |
Lists built-ins available without an import |
submilli__typescript__builtins__docs |
Returns declarations for selected built-ins |
Sessions, retries, and results
- The first program in a conversation opens a Submilli session, and later turns of the same
conversation reuse it. The session id is kept in the
store. The default store lives in memory; to reuse sessions after your application restarts, or from another worker, useSQLiteSessionStore("conversations.db")(it can share a file with the SDK'sSQLiteSession) or implementSessionStoreover your own database. Close the session withawait submilli.aclose_conversation(conversation_id). - Blueprint variables are fixed when the session opens. A later run in the same conversation
with different values raises
SubmilliSessionMismatchErrorbefore the model is called. - Harness secrets are sent when a session opens and are never stored. End the session before using rotated secrets.
- Each execute call sends an
Idempotency-Keyderived from the SDK's tool call id. A repeated call replays the recorded result. The toolkit reports conflicts and incomplete, in-progress, or unavailable replays without silently rerunning the program. - When a session has ended, the toolkit opens a replacement and tells the model. If a restarted server asks for the session's secrets, it rebinds them once and retries.
- On a server with durable execution, the toolkit waits for a queued run. A run that stops
without a result, such as one that is stuck or cancelled, comes back to the model as an error
with the run's
statusandreason. - The execute output the model reads contains
result,console,error,denied, and the program's application-tool calls (tool_calls: tool, arguments, outcome), so that after a refusal the model can say which calls already took effect. The output item'scustom_data, which the model never sees, containsexecution_id,session_id, and the same calls with theircall_ids, for linking to the audit trail. - Errors that are the application's raise: a bad token, an unknown Blueprint, invalid variables,
a missing conversation id, an unreachable server. Errors found while a tool runs reach you as
the cause of the SDK's
UserError. Program failures and denials are tool results.
Submilli enforces the Blueprint only for code that runs through Submilli. Other tools you give the agent keep their own permission boundaries.
Application tools
Let programs reuse your existing function tools. A model-written program can combine calls, loop over results, branch on conditions, and process the results in one run.
from agents import function_tool
@function_tool
def lookup_order(order_id: str) -> Order: ...
@function_tool(needs_approval=True)
def refund(order_id: str, amount: float) -> str: ...
submilli = SubmilliTools(blueprint="support", ptc=[lookup_order, refund])
agent = Agent(name="support", tools=submilli.tools)
The ptc parameter (programmatic tool calling) tells the toolkit which tools programs can use.
Submilli makes them available to the program through the
@tools Package,
so the program can import and call them. In your Blueprint, grant tools.call with a filter for
each allowed tool name.
Each call runs as an ordinary tool call of the SDK, with the run's context, the tool's
guardrails, timeout, and tracing. Input types come from the tool's schema, and return types
come from its output_json_schema or return annotation. Use a TypedDict (from
typing_extensions before Python 3.12) or a Pydantic model for typed objects. A raised
exception becomes an Error in the TypeScript program.
Tools in ptc are not given to the model directly. If you also pass them to the agent, direct
model calls bypass Submilli's Blueprint checks.
A session keeps the application-tool catalog it opened with. Changing the catalog causes the conversation's next call to open a new session.
Approvals
A tool with needs_approval pauses the program at each call that needs approval, the same way
the SDK pauses at a direct tool call. The run result's interruptions holds a
ToolApprovalItem for your tool, with the program's arguments. Approve or reject it on the run
state and run again:
result = await Runner.run(agent, "Refund order o_7", context=context)
while result.interruptions:
state = result.to_state()
for item in result.interruptions:
if ask_a_person(item.tool_name, item.arguments):
state.approve(item)
else:
state.reject(item, rejection_message="Refunds over $25 need a manager.")
result = await Runner.run(agent, state)
The program waits on the server meanwhile, up to the Blueprint's tool_call_timeout. A
rejection throws PermissionDeniedError in the program with your message. Each call that needs
approval pauses on its own, so a program that refunds three orders asks three times. The state
can be saved with to_string() and resumed with RunState.from_string in another process. The
program finishes in the session it started in; share the store too so that later turns of the
conversation keep using that session.
A run left waiting is not an answer. To go on without one, reject the pending calls on the state
and run it, which lets the program finish, or drop the unanswered call from the input you pass to
the next run: the Responses API refuses an input that holds a tool call without its output. A
dropped program keeps waiting on the server until tool_call_timeout, and its call never runs.
Submilli documentation
- Install the CLI and server
- Create and register a Blueprint
- Configure permissions
- Run the server
- Inspect the audit trail
- Use the HTTP API
Development
This package lives in the submilli-python workspace. See the repository README for workspace setup, checks, and releases.
uv run pytest # unit tests; the HTTP API is mocked
uv run pytest tests/integration_tests # starts a server from $SUBMILLI_BIN or PATH
The integration tests use a scripted model and need no model provider key.
Implementation notes are in NOTES.md.
License
Apache-2.0.
Metadata
Release files for openai-agents-submilli 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| openai_agents_submilli-0.1.0.tar.gz | 42.6 kB | With provenance | Details for openai_agents_submilli-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| openai_agents_submilli-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for openai_agents_submilli-0.1.0-py3-none-any.whl |
Total release size: 70.9 kB
Release files / openai_agents_submilli-0.1.0.tar.gz
| Download URL | openai_agents_submilli-0.1.0.tar.gz |
|---|---|
| Size | 42.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
3aabe01adf695f945e29f9bc05fdffbeab28f1d8e3b8fa0859a875697dee6f82
|
|
BLAKE2b-256 checksum How to use checksums |
19c17ac27c11f5c6d5714bd561186206dc9b478a8f8e4478ca4cb29cb7992e04
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 30, 2026.
Transparency logRelease files / openai_agents_submilli-0.1.0-py3-none-any.whl
| Download URL | openai_agents_submilli-0.1.0-py3-none-any.whl |
|---|---|
| Size | 28.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
09397bf13d55984da86f8ab46c4b49b9df9ac02ae8604dcac8ac9cfdd691b778
|
|
BLAKE2b-256 checksum How to use checksums |
79d39b722b024d119541808cb354137e342ad175fbf6be85ac0256bb25cc5415
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 30, 2026.
Transparency logSecurity for openai-agents-submilli 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for openai_agents_submilli-0.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 30, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.