Skip to content

Commit 922b9e4

Browse files
refactor: remove DBOS durable execution support
- Remove all DBOS integration code from agents, tools, and configuration - Eliminate DBOSAgent wrappers and workflow management across codebase - Remove DBOS-related environment variables and configuration options - Delete DBOS dependency from project requirements (pyproject.toml) - Remove DBOS-specific test infrastructure and integration tests - Simplify agent creation by removing conditional DBOS wrapping logic - Clean up MCP server handling by removing DBOS serialization workarounds - Update documentation to remove DBOS configuration section - Reduce external network dependencies (removed cloud.dbos.dev from whitelist)
1 parent f770c90 commit 922b9e4

20 files changed

Lines changed: 53 additions & 763 deletions

README.md

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -133,27 +133,6 @@ These providers are automatically configured with correct OpenAI-compatible endp
133133
- **⚠️ Unsupported Providers** - Providers like Amazon Bedrock and Google Vertex that require special authentication are clearly marked
134134
- **⚠️ No Tool Calling** - Models without tool calling support show a big warning since they can't use Code Puppy's file/shell tools
135135
136-
### Durable Execution
137-
138-
Code Puppy now supports **[DBOS](https://github.com/dbos-inc/dbos-transact-py)** durable execution.
139-
140-
When enabled, every agent is automatically wrapped as a `DBOSAgent`, checkpointing key interactions (including agent inputs, LLM responses, MCP calls, and tool calls) in a database for durability and recovery.
141-
142-
You can toggle DBOS via either of these options:
143-
144-
- CLI config (persists): `/set enable_dbos true` (or `false` to disable)
145-
146-
147-
Config takes precedence if set; otherwise the environment variable is used.
148-
149-
### Configuration
150-
151-
The following environment variables control DBOS behavior:
152-
- `DBOS_CONDUCTOR_KEY`: If set, Code Puppy connects to the [DBOS Management Console](https://console.dbos.dev/). Make sure you first register an app named `dbos-code-puppy` on the console to generate a Conductor key. Default: `None`.
153-
- `DBOS_LOG_LEVEL`: Logging verbosity: `CRITICAL`, `ERROR`, `WARNING`, `INFO`, or `DEBUG`. Default: `ERROR`.
154-
- `DBOS_SYSTEM_DATABASE_URL`: Database URL used by DBOS. Can point to a local SQLite file or a Postgres instance. Example: `postgresql://postgres:dbos@localhost:5432/postgres`. Default: `dbos_store.sqlite` file in the config directory.
155-
- `DBOS_APP_VERSION`: If set, Code Puppy uses it as the [DBOS application version](https://docs.dbos.dev/architecture#application-and-workflow-versions) and automatically tries to recover pending workflows for this version. Default: Code Puppy version + Unix timestamp in millisecond (disable automatic recovery).
156-
157136
### Custom Commands
158137
Create markdown files in `.claude/commands/`, `.github/prompts/`, or `.agents/commands/` to define custom slash commands. The filename becomes the command name and the content runs as a prompt.
159138

code_puppy/agents/base_agent.py

Lines changed: 37 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import mcp
2525
import pydantic
2626
import pydantic_ai.models
27-
from dbos import DBOS, SetWorkflowID
2827
from pydantic_ai import Agent as PydanticAgent
2928
from pydantic_ai import (
3029
BinaryContent,
@@ -35,7 +34,6 @@
3534
UsageLimitExceeded,
3635
UsageLimits,
3736
)
38-
from pydantic_ai.durable_exec.dbos import DBOSAgent
3937
from pydantic_ai.messages import (
4038
ModelMessage,
4139
ModelRequest,
@@ -56,7 +54,6 @@
5654
get_global_model_name,
5755
get_message_limit,
5856
get_protected_token_count,
59-
get_use_dbos,
6057
get_value,
6158
)
6259
from code_puppy.error_logging import log_error
@@ -1212,56 +1209,25 @@ def reload_code_generation_agent(self, message_group: Optional[str] = None):
12121209

12131210
self._last_model_name = resolved_model_name
12141211
# expose for run_with_mcp
1215-
# Wrap it with DBOS, but handle MCP servers separately to avoid serialization issues
12161212
global _reload_count
12171213
_reload_count += 1
1218-
if get_use_dbos():
1219-
# Don't pass MCP servers to the agent constructor when using DBOS
1220-
# This prevents the "cannot pickle async_generator object" error
1221-
# MCP servers will be handled separately in run_with_mcp
1222-
agent_without_mcp = PydanticAgent(
1223-
model=model,
1224-
instructions=instructions,
1225-
output_type=str,
1226-
retries=3,
1227-
toolsets=[], # Don't include MCP servers here
1228-
history_processors=[self.message_history_accumulator],
1229-
model_settings=model_settings,
1230-
)
1231-
1232-
# Register regular tools (non-MCP) on the new agent
1233-
agent_tools = self.get_available_tools()
1234-
register_tools_for_agent(agent_without_mcp, agent_tools)
1235-
1236-
# Wrap with DBOS
1237-
dbos_agent = DBOSAgent(
1238-
agent_without_mcp, name=f"{self.name}-{_reload_count}"
1239-
)
1240-
self.pydantic_agent = dbos_agent
1241-
self._code_generation_agent = dbos_agent
1214+
# Include filtered MCP servers in the agent
1215+
p_agent = PydanticAgent(
1216+
model=model,
1217+
instructions=instructions,
1218+
output_type=str,
1219+
retries=3,
1220+
toolsets=filtered_mcp_servers if filtered_mcp_servers else [],
1221+
history_processors=[self.message_history_accumulator],
1222+
model_settings=model_settings,
1223+
)
1224+
# Register regular tools on the agent
1225+
agent_tools = self.get_available_tools()
1226+
register_tools_for_agent(p_agent, agent_tools)
12421227

1243-
# Store filtered MCP servers separately for runtime use
1244-
self._mcp_servers = filtered_mcp_servers
1245-
else:
1246-
# Normal path without DBOS - include filtered MCP servers in the agent
1247-
# Re-create agent with filtered MCP servers
1248-
p_agent = PydanticAgent(
1249-
model=model,
1250-
instructions=instructions,
1251-
output_type=str,
1252-
retries=3,
1253-
toolsets=filtered_mcp_servers,
1254-
history_processors=[self.message_history_accumulator],
1255-
model_settings=model_settings,
1256-
)
1257-
# Register regular tools on the agent
1258-
agent_tools = self.get_available_tools()
1259-
register_tools_for_agent(p_agent, agent_tools)
1260-
1261-
self.pydantic_agent = p_agent
1262-
self._code_generation_agent = p_agent
1263-
self._mcp_servers = filtered_mcp_servers
1264-
self._mcp_servers = mcp_servers
1228+
self.pydantic_agent = p_agent
1229+
self._code_generation_agent = p_agent
1230+
self._mcp_servers = filtered_mcp_servers
12651231
return self._code_generation_agent
12661232

12671233
def _create_agent_with_output_type(self, output_type: Type[Any]) -> PydanticAgent:
@@ -1275,7 +1241,7 @@ def _create_agent_with_output_type(self, output_type: Type[Any]) -> PydanticAgen
12751241
output_type: The Pydantic model or type for structured output.
12761242
12771243
Returns:
1278-
A configured PydanticAgent (or DBOSAgent wrapper) with the custom output_type.
1244+
A configured PydanticAgent with the custom output_type.
12791245
"""
12801246
from code_puppy.model_utils import prepare_prompt_for_model
12811247
from code_puppy.tools import register_tools_for_agent
@@ -1302,38 +1268,19 @@ def _create_agent_with_output_type(self, output_type: Type[Any]) -> PydanticAgen
13021268
global _reload_count
13031269
_reload_count += 1
13041270

1305-
if get_use_dbos():
1306-
temp_agent = PydanticAgent(
1307-
model=model,
1308-
instructions=instructions,
1309-
output_type=output_type,
1310-
retries=3,
1311-
toolsets=[],
1312-
history_processors=[self.message_history_accumulator],
1313-
model_settings=model_settings,
1314-
)
1315-
agent_tools = self.get_available_tools()
1316-
register_tools_for_agent(temp_agent, agent_tools)
1317-
dbos_agent = DBOSAgent(
1318-
temp_agent, name=f"{self.name}-structured-{_reload_count}"
1319-
)
1320-
return dbos_agent
1321-
else:
1322-
temp_agent = PydanticAgent(
1323-
model=model,
1324-
instructions=instructions,
1325-
output_type=output_type,
1326-
retries=3,
1327-
toolsets=mcp_servers,
1328-
history_processors=[self.message_history_accumulator],
1329-
model_settings=model_settings,
1330-
)
1331-
agent_tools = self.get_available_tools()
1332-
register_tools_for_agent(temp_agent, agent_tools)
1333-
return temp_agent
1271+
temp_agent = PydanticAgent(
1272+
model=model,
1273+
instructions=instructions,
1274+
output_type=output_type,
1275+
retries=3,
1276+
toolsets=mcp_servers,
1277+
history_processors=[self.message_history_accumulator],
1278+
model_settings=model_settings,
1279+
)
1280+
agent_tools = self.get_available_tools()
1281+
register_tools_for_agent(temp_agent, agent_tools)
1282+
return temp_agent
13341283

1335-
# It's okay to decorate it with DBOS.step even if not using DBOS; the decorator is a no-op in that case.
1336-
@DBOS.step()
13371284
def message_history_accumulator(self, ctx: RunContext, messages: List[Any]):
13381285
_message_history = self.get_message_history()
13391286
message_history_hashes = set([self.hash_message(m) for m in _message_history])
@@ -1841,49 +1788,14 @@ async def run_agent_task():
18411788

18421789
usage_limits = UsageLimits(request_limit=get_message_limit())
18431790

1844-
# Handle MCP servers - add them temporarily when using DBOS
1845-
if (
1846-
get_use_dbos()
1847-
and hasattr(self, "_mcp_servers")
1848-
and self._mcp_servers
1849-
):
1850-
# Temporarily add MCP servers to the DBOS agent using internal _toolsets
1851-
original_toolsets = pydantic_agent._toolsets
1852-
pydantic_agent._toolsets = original_toolsets + self._mcp_servers
1853-
pydantic_agent._toolsets = original_toolsets + self._mcp_servers
1854-
1855-
try:
1856-
# Set the workflow ID for DBOS context so DBOS and Code Puppy ID match
1857-
with SetWorkflowID(group_id):
1858-
result_ = await pydantic_agent.run(
1859-
prompt_payload,
1860-
message_history=self.get_message_history(),
1861-
usage_limits=usage_limits,
1862-
event_stream_handler=self._event_stream_handler,
1863-
**kwargs,
1864-
)
1865-
finally:
1866-
# Always restore original toolsets
1867-
pydantic_agent._toolsets = original_toolsets
1868-
elif get_use_dbos():
1869-
# DBOS without MCP servers
1870-
with SetWorkflowID(group_id):
1871-
result_ = await pydantic_agent.run(
1872-
prompt_payload,
1873-
message_history=self.get_message_history(),
1874-
usage_limits=usage_limits,
1875-
event_stream_handler=self._event_stream_handler,
1876-
**kwargs,
1877-
)
1878-
else:
1879-
# Non-DBOS path (MCP servers are already included)
1880-
result_ = await pydantic_agent.run(
1881-
prompt_payload,
1882-
message_history=self.get_message_history(),
1883-
usage_limits=usage_limits,
1884-
event_stream_handler=self._event_stream_handler,
1885-
**kwargs,
1886-
)
1791+
# MCP servers are already included in the agent
1792+
result_ = await pydantic_agent.run(
1793+
prompt_payload,
1794+
message_history=self.get_message_history(),
1795+
usage_limits=usage_limits,
1796+
event_stream_handler=self._event_stream_handler,
1797+
**kwargs,
1798+
)
18871799
return result_
18881800
except* UsageLimitExceeded as ule:
18891801
emit_info(f"Usage limit exceeded: {str(ule)}", group_id=group_id)
@@ -1899,12 +1811,8 @@ async def run_agent_task():
18991811
)
19001812
except* asyncio.exceptions.CancelledError:
19011813
emit_info("Cancelled")
1902-
if get_use_dbos():
1903-
await DBOS.cancel_workflow_async(group_id)
19041814
except* InterruptedError as ie:
19051815
emit_info(f"Interrupted: {str(ie)}")
1906-
if get_use_dbos():
1907-
await DBOS.cancel_workflow_async(group_id)
19081816
except* Exception as other_error:
19091817
# Filter out CancelledError and UsageLimitExceeded from the exception group - let it propagate
19101818
remaining_exceptions = []

code_puppy/cli_runner.py

Lines changed: 0 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,9 @@
1212
import asyncio
1313
import os
1414
import sys
15-
import time
1615
import traceback
1716
from pathlib import Path
1817

19-
from dbos import DBOS, DBOSConfig
2018
from rich.console import Console
2119

2220
from code_puppy import __version__, callbacks, plugins
@@ -26,10 +24,8 @@
2624
from code_puppy.config import (
2725
AUTOSAVE_DIR,
2826
COMMAND_HISTORY_FILE,
29-
DBOS_DATABASE_URL,
3027
ensure_config_exists,
3128
finalize_autosave_session,
32-
get_use_dbos,
3329
initialize_command_history_file,
3430
save_command_to_history,
3531
)
@@ -287,33 +283,6 @@ def _uvx_protective_sigint_handler(_sig, _frame):
287283

288284
await callbacks.on_startup()
289285

290-
# Initialize DBOS if not disabled
291-
if get_use_dbos():
292-
# Append a Unix timestamp in ms to the version for uniqueness
293-
dbos_app_version = os.environ.get(
294-
"DBOS_APP_VERSION", f"{current_version}-{int(time.time() * 1000)}"
295-
)
296-
dbos_config: DBOSConfig = {
297-
"name": "dbos-code-puppy",
298-
"system_database_url": DBOS_DATABASE_URL,
299-
"run_admin_server": False,
300-
"conductor_key": os.environ.get(
301-
"DBOS_CONDUCTOR_KEY"
302-
), # Optional, if set in env, connect to conductor
303-
"log_level": os.environ.get(
304-
"DBOS_LOG_LEVEL", "ERROR"
305-
), # Default to ERROR level to suppress verbose logs
306-
"application_version": dbos_app_version, # Match DBOS app version to Code Puppy version
307-
}
308-
try:
309-
DBOS(config=dbos_config)
310-
DBOS.launch()
311-
except Exception as e:
312-
emit_error(f"Error initializing DBOS: {e}")
313-
sys.exit(1)
314-
else:
315-
pass
316-
317286
global shutdown_flag
318287
shutdown_flag = False
319288
try:
@@ -338,8 +307,6 @@ def _uvx_protective_sigint_handler(_sig, _frame):
338307
if bus_renderer:
339308
bus_renderer.stop()
340309
await callbacks.on_shutdown()
341-
if get_use_dbos():
342-
DBOS.destroy()
343310

344311

345312
async def interactive_mode(message_renderer, initial_command: str = None) -> None:
@@ -907,8 +874,6 @@ def main_entry():
907874
except KeyboardInterrupt:
908875
# Note: Using sys.stderr for crash output - messaging system may not be available
909876
sys.stderr.write(traceback.format_exc())
910-
if get_use_dbos():
911-
DBOS.destroy()
912877
return 0
913878
finally:
914879
# Reset terminal on Unix-like systems (not Windows)

code_puppy/command_line/config_commands.py

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ def handle_show_command(command: str) -> bool:
4343
get_protected_token_count,
4444
get_puppy_name,
4545
get_temperature,
46-
get_use_dbos,
4746
get_yolo_mode,
4847
)
4948
from code_puppy.keymap import get_cancel_agent_display_name
@@ -72,7 +71,6 @@ def handle_show_command(command: str) -> bool:
7271
[bold]default_agent:[/bold] [cyan]{default_agent}[/cyan]
7372
[bold]model:[/bold] [green]{model}[/green]
7473
[bold]YOLO_MODE:[/bold] {"[red]ON[/red]" if yolo_mode else "[yellow]off[/yellow]"}
75-
[bold]DBOS:[/bold] {"[green]enabled[/green]" if get_use_dbos() else "[yellow]disabled[/yellow]"} (toggle: /set enable_dbos true|false)
7674
[bold]auto_save_session:[/bold] {"[green]enabled[/green]" if auto_save else "[yellow]disabled[/yellow]"}
7775
[bold]protected_tokens:[/bold] [cyan]{protected_tokens:,}[/cyan] recent tokens preserved
7876
[bold]compaction_threshold:[/bold] [cyan]{compaction_threshold:.1%}[/cyan] context usage triggers compaction
@@ -213,14 +211,6 @@ def handle_set_command(command: str) -> bool:
213211
)
214212
return True
215213
if key:
216-
# Check if we're toggling DBOS enablement
217-
if key == "enable_dbos":
218-
emit_info(
219-
Text.from_markup(
220-
"[yellow]⚠️ DBOS configuration changed. Please restart Code Puppy for this change to take effect.[/yellow]"
221-
)
222-
)
223-
224214
# Validate cancel_agent_key before setting
225215
if key == "cancel_agent_key":
226216
from code_puppy.keymap import VALID_CANCEL_KEYS

0 commit comments

Comments
 (0)