polos-sdk 0.1.0__tar.gz → 0.1.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/BUILD.md +17 -0
  2. polos_sdk-0.1.2/PKG-INFO +121 -0
  3. polos_sdk-0.1.2/README.md +70 -0
  4. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/polos/agents/agent.py +3 -0
  5. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/polos/agents/stream.py +19 -3
  6. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/polos/core/step.py +7 -0
  7. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/polos/core/workflow.py +6 -0
  8. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/polos/features/events.py +5 -10
  9. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/polos/runtime/client.py +8 -4
  10. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/polos/runtime/worker.py +5 -3
  11. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/polos/tools/tool.py +1 -0
  12. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/polos/utils/serializer.py +21 -1
  13. polos_sdk-0.1.0/PKG-INFO +0 -650
  14. polos_sdk-0.1.0/README.md +0 -599
  15. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/.gitignore +0 -0
  16. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/polos/__init__.py +0 -0
  17. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/polos/agents/__init__.py +0 -0
  18. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/polos/agents/conversation_history.py +0 -0
  19. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/polos/agents/stop_conditions.py +0 -0
  20. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/polos/core/__init__.py +0 -0
  21. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/polos/core/context.py +0 -0
  22. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/polos/core/state.py +0 -0
  23. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/polos/features/__init__.py +0 -0
  24. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/polos/features/schedules.py +0 -0
  25. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/polos/features/tracing.py +0 -0
  26. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/polos/features/wait.py +0 -0
  27. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/polos/llm/__init__.py +0 -0
  28. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/polos/llm/generate.py +0 -0
  29. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/polos/llm/providers/__init__.py +0 -0
  30. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/polos/llm/providers/anthropic.py +0 -0
  31. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/polos/llm/providers/azure.py +0 -0
  32. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/polos/llm/providers/base.py +0 -0
  33. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/polos/llm/providers/fireworks.py +0 -0
  34. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/polos/llm/providers/gemini.py +0 -0
  35. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/polos/llm/providers/groq.py +0 -0
  36. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/polos/llm/providers/openai.py +0 -0
  37. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/polos/llm/providers/together.py +0 -0
  38. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/polos/llm/stream.py +0 -0
  39. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/polos/middleware/__init__.py +0 -0
  40. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/polos/middleware/guardrail.py +0 -0
  41. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/polos/middleware/guardrail_executor.py +0 -0
  42. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/polos/middleware/hook.py +0 -0
  43. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/polos/middleware/hook_executor.py +0 -0
  44. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/polos/runtime/__init__.py +0 -0
  45. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/polos/runtime/batch.py +0 -0
  46. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/polos/runtime/queue.py +0 -0
  47. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/polos/runtime/worker_server.py +0 -0
  48. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/polos/tools/__init__.py +0 -0
  49. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/polos/types/__init__.py +0 -0
  50. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/polos/types/types.py +0 -0
  51. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/polos/utils/__init__.py +0 -0
  52. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/polos/utils/agent.py +0 -0
  53. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/polos/utils/client_context.py +0 -0
  54. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/polos/utils/config.py +0 -0
  55. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/polos/utils/output_schema.py +0 -0
  56. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/polos/utils/retry.py +0 -0
  57. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/polos/utils/tracing.py +0 -0
  58. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/polos/utils/worker_singleton.py +0 -0
  59. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/pyproject.toml +0 -0
  60. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/tests/__init__.py +0 -0
  61. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/tests/conftest.py +0 -0
  62. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/tests/integration/__init__.py +0 -0
  63. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/tests/integration/test_agent_execution.py +0 -0
  64. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/tests/integration/test_runtime_interactions.py +0 -0
  65. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/tests/integration/test_tool_execution.py +0 -0
  66. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/tests/integration/test_workflow_execution.py +0 -0
  67. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/tests/unit/__init__.py +0 -0
  68. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/tests/unit/test_agents/__init__.py +0 -0
  69. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/tests/unit/test_agents/test_agent.py +0 -0
  70. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/tests/unit/test_agents/test_conversation_history.py +0 -0
  71. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/tests/unit/test_agents/test_stop_conditions.py +0 -0
  72. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/tests/unit/test_agents/test_stream.py +0 -0
  73. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/tests/unit/test_core/__init__.py +0 -0
  74. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/tests/unit/test_core/test_context.py +0 -0
  75. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/tests/unit/test_core/test_state.py +0 -0
  76. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/tests/unit/test_core/test_step.py +0 -0
  77. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/tests/unit/test_core/test_workflow.py +0 -0
  78. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/tests/unit/test_llm/__init__.py +0 -0
  79. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/tests/unit/test_llm/test_providers_base.py +0 -0
  80. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/tests/unit/test_llm/test_utils.py +0 -0
  81. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/tests/unit/test_middleware/__init__.py +0 -0
  82. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/tests/unit/test_middleware/test_guardrail.py +0 -0
  83. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/tests/unit/test_middleware/test_hook.py +0 -0
  84. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/tests/unit/test_runtime/__init__.py +0 -0
  85. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/tests/unit/test_runtime/test_client.py +0 -0
  86. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/tests/unit/test_utils/__init__.py +0 -0
  87. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/tests/unit/test_utils/test_config.py +0 -0
  88. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/tests/unit/test_utils/test_output_schema.py +0 -0
  89. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/tests/unit/test_utils/test_retry.py +0 -0
  90. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/tests/unit/test_utils/test_serializer.py +0 -0
  91. {polos_sdk-0.1.0 → polos_sdk-0.1.2}/uv.lock +0 -0
@@ -51,6 +51,23 @@ uv run ruff check .
51
51
  uv run ruff check --fix .
52
52
  ```
53
53
 
54
+ #### Using uvx (Alternative)
55
+
56
+ If `uv run` fails due to version detection issues (e.g., missing git tags), use `uvx` to run tools directly without building the package:
57
+
58
+ ```bash
59
+ # Format code
60
+ uvx ruff format .
61
+
62
+ # Lint code
63
+ uvx ruff check .
64
+
65
+ # Auto-fix linting issues
66
+ uvx ruff check --fix .
67
+ ```
68
+
69
+ `uvx` runs the tool in an isolated environment without needing to install or build the SDK package.
70
+
54
71
  ## Building the Package
55
72
 
56
73
  ### Build Locally
@@ -0,0 +1,121 @@
1
+ Metadata-Version: 2.4
2
+ Name: polos-sdk
3
+ Version: 0.1.2
4
+ Summary: Polos SDK for Python - Durable Agent Execution
5
+ Project-URL: Homepage, https://github.com/polos-dev/polos
6
+ Project-URL: Repository, https://github.com/polos-dev/polos
7
+ Project-URL: Documentation, https://docs.polos.dev
8
+ Project-URL: Issues, https://github.com/polos-dev/polos/issues
9
+ Author: Polos Team
10
+ License: Apache-2.0
11
+ Keywords: agents,ai,async,durable-execution,llm,orchestration,workflow
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: Apache Software License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
20
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
+ Requires-Python: >=3.10
22
+ Requires-Dist: anyio>=4.0.0
23
+ Requires-Dist: fastapi>=0.104.0
24
+ Requires-Dist: httpx>=0.24.0
25
+ Requires-Dist: opentelemetry-api>=1.20.0
26
+ Requires-Dist: opentelemetry-exporter-otlp-proto-grpc>=1.20.0
27
+ Requires-Dist: opentelemetry-sdk>=1.20.0
28
+ Requires-Dist: pydantic>=2.0.0
29
+ Requires-Dist: python-dotenv>=1.0.0
30
+ Requires-Dist: uvicorn>=0.24.0
31
+ Provides-Extra: anthropic
32
+ Requires-Dist: anthropic>=0.39.0; extra == 'anthropic'
33
+ Provides-Extra: dev
34
+ Requires-Dist: pre-commit>=3.0.0; extra == 'dev'
35
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
36
+ Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
37
+ Requires-Dist: pytest-mock>=3.10.0; extra == 'dev'
38
+ Requires-Dist: pytest>=7.0.0; extra == 'dev'
39
+ Requires-Dist: ruff>=0.1.0; extra == 'dev'
40
+ Provides-Extra: fireworks
41
+ Requires-Dist: openai>=1.0.0; extra == 'fireworks'
42
+ Provides-Extra: gemini
43
+ Requires-Dist: openai>=1.0.0; extra == 'gemini'
44
+ Provides-Extra: groq
45
+ Requires-Dist: openai>=1.0.0; extra == 'groq'
46
+ Provides-Extra: openai
47
+ Requires-Dist: openai>=1.0.0; extra == 'openai'
48
+ Provides-Extra: together
49
+ Requires-Dist: openai>=1.0.0; extra == 'together'
50
+ Description-Content-Type: text/markdown
51
+
52
+ # Polos Python SDK
53
+
54
+ Durable execution engine for Python. Build reliable AI agents and workflows that can survive failures, handle long-running tasks, and coordinate complex processes.
55
+
56
+ ## Features
57
+
58
+ - 🤖 **AI Agents** - Build LLM-powered agents with tool calling, streaming, and conversation history
59
+ - 🔄 **Durable Workflows** - Workflows survive failures and resume from checkpoints
60
+ - ⏰ **Long-Running** - Execute workflows that run for hours or days
61
+ - 🔗 **Workflow Orchestration** - Chain workflows together and build complex processes
62
+ - 🛠️ **Tools** - Define reusable tools that agents can call
63
+ - 🐍 **Native Python** - Async/await support, type hints, and Pythonic APIs
64
+ - 📊 **Observability** - Built-in tracing, events, and monitoring
65
+
66
+ ## Installation
67
+
68
+ ```bash
69
+ pip install polos-sdk
70
+ ```
71
+
72
+ Or with UV (recommended):
73
+ ```bash
74
+ uv add polos-sdk
75
+ ```
76
+
77
+ ### Optional Dependencies
78
+
79
+ Install provider-specific dependencies for LLM support:
80
+
81
+ ```bash
82
+ # OpenAI
83
+ pip install polos-sdk[openai]
84
+
85
+ # Anthropic
86
+ pip install polos-sdk[anthropic]
87
+
88
+ # Google Gemini
89
+ pip install polos-sdk[gemini]
90
+
91
+ # Groq
92
+ pip install polos-sdk[groq]
93
+
94
+ # Fireworks
95
+ pip install polos-sdk[fireworks]
96
+
97
+ # Together AI
98
+ pip install polos-sdk[together]
99
+
100
+ # All providers
101
+ pip install polos-sdk[openai,anthropic,gemini,groq,fireworks,together]
102
+ ```
103
+
104
+ ## Quick Start
105
+
106
+ Use the quickstart guide at [https://docs.polos.dev](https://docs.polos.dev) to get started in minutes.
107
+
108
+ ## License
109
+
110
+ Apache-2.0 - see [LICENSE](../../LICENSE) for details.
111
+
112
+ ## Support
113
+
114
+ - 📖 [Documentation](https://docs.polos.dev)
115
+ - 💬 [Discord Community](https://discord.gg/ZAxHKMPwFG)
116
+ - 🐛 [Issue Tracker](https://github.com/polos-dev/polos/issues)
117
+ - 📧 [Email Support](mailto:support@polos.dev)
118
+
119
+ ---
120
+
121
+ Built with ❤️ by the Polos team
@@ -0,0 +1,70 @@
1
+ # Polos Python SDK
2
+
3
+ Durable execution engine for Python. Build reliable AI agents and workflows that can survive failures, handle long-running tasks, and coordinate complex processes.
4
+
5
+ ## Features
6
+
7
+ - 🤖 **AI Agents** - Build LLM-powered agents with tool calling, streaming, and conversation history
8
+ - 🔄 **Durable Workflows** - Workflows survive failures and resume from checkpoints
9
+ - ⏰ **Long-Running** - Execute workflows that run for hours or days
10
+ - 🔗 **Workflow Orchestration** - Chain workflows together and build complex processes
11
+ - 🛠️ **Tools** - Define reusable tools that agents can call
12
+ - 🐍 **Native Python** - Async/await support, type hints, and Pythonic APIs
13
+ - 📊 **Observability** - Built-in tracing, events, and monitoring
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ pip install polos-sdk
19
+ ```
20
+
21
+ Or with UV (recommended):
22
+ ```bash
23
+ uv add polos-sdk
24
+ ```
25
+
26
+ ### Optional Dependencies
27
+
28
+ Install provider-specific dependencies for LLM support:
29
+
30
+ ```bash
31
+ # OpenAI
32
+ pip install polos-sdk[openai]
33
+
34
+ # Anthropic
35
+ pip install polos-sdk[anthropic]
36
+
37
+ # Google Gemini
38
+ pip install polos-sdk[gemini]
39
+
40
+ # Groq
41
+ pip install polos-sdk[groq]
42
+
43
+ # Fireworks
44
+ pip install polos-sdk[fireworks]
45
+
46
+ # Together AI
47
+ pip install polos-sdk[together]
48
+
49
+ # All providers
50
+ pip install polos-sdk[openai,anthropic,gemini,groq,fireworks,together]
51
+ ```
52
+
53
+ ## Quick Start
54
+
55
+ Use the quickstart guide at [https://docs.polos.dev](https://docs.polos.dev) to get started in minutes.
56
+
57
+ ## License
58
+
59
+ Apache-2.0 - see [LICENSE](../../LICENSE) for details.
60
+
61
+ ## Support
62
+
63
+ - 📖 [Documentation](https://docs.polos.dev)
64
+ - 💬 [Discord Community](https://discord.gg/ZAxHKMPwFG)
65
+ - 🐛 [Issue Tracker](https://github.com/polos-dev/polos/issues)
66
+ - 📧 [Email Support](mailto:support@polos.dev)
67
+
68
+ ---
69
+
70
+ Built with ❤️ by the Polos team
@@ -351,6 +351,7 @@ class Agent(Workflow):
351
351
  id: str, # Required: task ID
352
352
  provider: str,
353
353
  model: str,
354
+ description: str | None = None, # Description for team coordination
354
355
  system_prompt: str | None = None,
355
356
  tools: list[Any] | None = None,
356
357
  temperature: float | None = None,
@@ -388,6 +389,7 @@ class Agent(Workflow):
388
389
  super().__init__(
389
390
  id=id,
390
391
  func=self._agent_execute,
392
+ description=description,
391
393
  workflow_type="agent",
392
394
  queue_name=queue_name,
393
395
  queue_concurrency_limit=queue_concurrency_limit,
@@ -689,6 +691,7 @@ class Agent(Workflow):
689
691
  "input": input,
690
692
  "session_id": session_id,
691
693
  "user_id": user_id,
694
+ "conversation_id": conversation_id,
692
695
  "streaming": True,
693
696
  "provider_kwargs": kwargs, # Pass kwargs to provider
694
697
  },
@@ -56,7 +56,6 @@ async def _agent_stream_function(ctx: AgentContext, payload: dict[str, Any]) ->
56
56
  agent_run_id = ctx.execution_id # Use execution_id from context
57
57
  agent_config = payload["agent_config"]
58
58
  streaming = payload.get("streaming", True) # Default to True for backward compatibility
59
- tool_stop_action = False
60
59
  input_data = payload.get("input")
61
60
 
62
61
  result = {
@@ -206,6 +205,23 @@ async def _agent_stream_function(ctx: AgentContext, payload: dict[str, Any]) ->
206
205
  "tool_results": tool_results, # Tool results from previous iteration
207
206
  },
208
207
  )
208
+
209
+ if guardrails and streaming and llm_result.get("content"):
210
+ # Emit one text_delta event with full response for clients who are streaming
211
+ await ctx.step.publish_event(
212
+ f"llm_generate:text_delta:{agent_step}",
213
+ topic=f"workflow:{agent_run_id}",
214
+ event_type="text_delta",
215
+ data={
216
+ "step": agent_step,
217
+ "chunk_index": 1,
218
+ "content": llm_result.get("content"),
219
+ "_metadata": {
220
+ "execution_id": agent_run_id,
221
+ "workflow_id": ctx.workflow_id,
222
+ },
223
+ },
224
+ )
209
225
  else:
210
226
  # No guardrails - use streaming
211
227
  llm_result = await _llm_stream(
@@ -313,7 +329,7 @@ async def _agent_stream_function(ctx: AgentContext, payload: dict[str, Any]) ->
313
329
  )
314
330
 
315
331
  # Execute all tools in batch
316
- if tool_stop_action is False and len(batch_workflows) > 0:
332
+ if len(batch_workflows) > 0:
317
333
  tool_results_list: list[BatchStepResult] = await ctx.step.batch_invoke_and_wait(
318
334
  f"execute_tools:step_{agent_step}", batch_workflows
319
335
  )
@@ -453,7 +469,7 @@ async def _agent_stream_function(ctx: AgentContext, payload: dict[str, Any]) ->
453
469
  raise StepExecutionError(hook_result.error_message or "Hook execution failed")
454
470
 
455
471
  # No tool results, we're done
456
- if tool_results is None or len(tool_results) == 0 or tool_stop_action:
472
+ if tool_results is None or len(tool_results) == 0:
457
473
  end_steps = True
458
474
 
459
475
  # Evaluate stop conditions (if any)
@@ -134,6 +134,13 @@ class Step:
134
134
  # Extract full module path for Pydantic class
135
135
  # (e.g., "polos.llm.providers.base.LLMResponse")
136
136
  output_schema_name = f"{result.__class__.__module__}.{result.__class__.__name__}"
137
+ elif isinstance(result, list) and result and isinstance(result[0], BaseModel):
138
+ # Handle list of Pydantic models
139
+ outputs = [item.model_dump(mode="json") for item in result]
140
+ # Store schema name of the list item type for deserialization
141
+ output_schema_name = (
142
+ f"list[{result[0].__class__.__module__}.{result[0].__class__.__name__}]"
143
+ )
137
144
  else:
138
145
  outputs = result
139
146
 
@@ -80,6 +80,7 @@ class Workflow:
80
80
  self,
81
81
  id: str,
82
82
  func: Callable,
83
+ description: str | None = None, # Description for team coordination
83
84
  workflow_type: str | None = "workflow",
84
85
  queue_name: str | None = None,
85
86
  queue_concurrency_limit: int | None = None,
@@ -94,6 +95,7 @@ class Workflow:
94
95
  state_schema: type[BaseModel] | None = None,
95
96
  ):
96
97
  self.id = id
98
+ self.description = description # Description for team coordination
97
99
  self.func = func
98
100
  self.is_async = asyncio.iscoroutinefunction(func)
99
101
  # Check if function has a payload parameter
@@ -849,6 +851,7 @@ class Workflow:
849
851
 
850
852
  def workflow(
851
853
  id: str | None = None,
854
+ description: str | None = None, # Description for team coordination
852
855
  queue: str | Queue | dict[str, Any] | None = None,
853
856
  trigger_on_event: str | None = None,
854
857
  batch_size: int = 1,
@@ -938,6 +941,8 @@ def workflow(
938
941
 
939
942
  Args:
940
943
  id: Optional workflow ID (defaults to function name)
944
+ description: Optional description for team coordination. Used when this
945
+ workflow is added to a Team so the coordinator LLM understands its purpose.
941
946
  queue: Optional queue configuration. Can be:
942
947
  - str: Queue name
943
948
  - Queue: Queue object
@@ -1158,6 +1163,7 @@ def workflow(
1158
1163
  workflow_obj = Workflow(
1159
1164
  id=workflow_id,
1160
1165
  func=func,
1166
+ description=description,
1161
1167
  queue_name=queue_name,
1162
1168
  queue_concurrency_limit=queue_concurrency_limit,
1163
1169
  trigger_on_event=trigger_on_event,
@@ -235,8 +235,7 @@ async def batch_publish(
235
235
  async def publish(
236
236
  client: PolosClient,
237
237
  topic: str,
238
- event_type: str | None = None,
239
- data: dict[str, Any] = None,
238
+ event_data: EventData,
240
239
  execution_id: str | None = None,
241
240
  root_execution_id: str | None = None,
242
241
  ) -> int:
@@ -247,20 +246,16 @@ async def publish(
247
246
  Args:
248
247
  client: PolosClient instance
249
248
  topic: Event topic
250
- event_type: Optional type of event
251
- data: Event payload
249
+ event_data: EventData
250
+ execution_id: Optional execution ID
251
+ root_execution_id: Optional root execution ID
252
252
 
253
253
  Returns:
254
254
  sequence_id: Global sequence ID for the event
255
255
  """
256
256
  sequence_ids = await batch_publish(
257
257
  topic=topic,
258
- events=[
259
- EventData(
260
- event_type=event_type,
261
- data=data or {},
262
- )
263
- ],
258
+ events=[event_data],
264
259
  execution_id=execution_id,
265
260
  root_execution_id=root_execution_id,
266
261
  client=client,
@@ -48,17 +48,22 @@ class PolosClient:
48
48
  api_url: str | None = None,
49
49
  api_key: str | None = None,
50
50
  project_id: str | None = None,
51
+ deployment_id: str | None = None,
51
52
  ):
52
53
  """Initialize Polos client.
53
54
 
54
55
  Args:
55
- api_url: Orchestrator API URL (default: from POLOS_API_URL env var or http://localhost:8080)
56
+ api_url: Orchestrator API URL (default: from POLOS_API_URL env var or
57
+ http://localhost:8080)
56
58
  api_key: API key for authentication (default: from POLOS_API_KEY env var)
57
59
  project_id: Project ID for multi-tenancy (default: from POLOS_PROJECT_ID env var)
60
+ deployment_id: Deployment ID for workflow invocations (default: from
61
+ POLOS_DEPLOYMENT_ID env var, None uses latest deployment)
58
62
  """
59
63
  self.api_url = api_url or os.getenv("POLOS_API_URL", "http://localhost:8080")
60
64
  self.api_key = api_key or os.getenv("POLOS_API_KEY")
61
65
  self.project_id = project_id or os.getenv("POLOS_PROJECT_ID")
66
+ self.deployment_id = deployment_id or os.getenv("POLOS_DEPLOYMENT_ID")
62
67
 
63
68
  # Validate required fields (with local mode support)
64
69
  local_mode_requested = os.getenv("POLOS_LOCAL_MODE", "False").lower() == "true"
@@ -158,7 +163,6 @@ class PolosClient:
158
163
  Args:
159
164
  workflow_id: The workflow identifier
160
165
  payload: The workflow payload
161
- deployment_id: Optional deployment ID (if not provided, uses latest active)
162
166
  queue_name: Optional queue name (if not provided, defaults to workflow_id)
163
167
  queue_concurrency_limit: Optional concurrency limit for queue creation
164
168
  concurrency_key: Optional concurrency key for per-tenant queuing
@@ -172,7 +176,7 @@ class PolosClient:
172
176
  """
173
177
  return await self._submit_workflow(
174
178
  workflow_id=workflow_id,
175
- deployment_id=None, # Use latest deployment
179
+ deployment_id=self.deployment_id, # Use client's deployment_id (None uses latest)
176
180
  payload=payload,
177
181
  queue_name=queue_name,
178
182
  queue_concurrency_limit=queue_concurrency_limit,
@@ -235,7 +239,7 @@ class PolosClient:
235
239
  # Submit all workflows in a single batch using the batch endpoint
236
240
  handles = await self._submit_workflows(
237
241
  workflows=workflow_requests,
238
- deployment_id=None, # Use latest active deployment
242
+ deployment_id=self.deployment_id, # Use client's deployment_id (None uses latest)
239
243
  parent_execution_id=None,
240
244
  root_execution_id=None,
241
245
  step_key=None, # Not invoked from a step, so no step_key
@@ -187,8 +187,8 @@ class Worker:
187
187
 
188
188
  # Build workflow registry
189
189
  self.workflows_registry: dict[str, Workflow] = {}
190
- self.agents: list[Agent] = [a for a in agents if isinstance(a, Agent)] or []
191
- self.tools: list[Tool] = [t for t in tools if isinstance(t, Tool)] or []
190
+ self.agents: list[Agent] = [a for a in (agents or []) if isinstance(a, Agent)] or []
191
+ self.tools: list[Tool] = [t for t in (tools or []) if isinstance(t, Tool)] or []
192
192
  self.agent_ids: list[str] = []
193
193
  self.tool_ids: list[str] = []
194
194
  self.workflow_ids: list[str] = []
@@ -938,7 +938,9 @@ class Worker:
938
938
  # Check if error is StepExecutionError - if so, mark as non-retryable
939
939
  # Tools are not retryable by default. We feed the error back to the LLM to handle.
940
940
  retryable = (
941
- not isinstance(error, StepExecutionError) and workflow.workflow_type != "tool"
941
+ workflow
942
+ and not isinstance(error, StepExecutionError)
943
+ and workflow.workflow_type != "tool"
942
944
  )
943
945
  await self._report_failure(
944
946
  workflow_data["execution_id"], error_message, stack_trace, retryable=retryable
@@ -63,6 +63,7 @@ class Tool(Workflow):
63
63
  id=id,
64
64
  func=func or self._default_execute,
65
65
  workflow_type="tool",
66
+ description=description or self.__doc__ or "",
66
67
  queue_name=queue_name,
67
68
  queue_concurrency_limit=queue_concurrency_limit,
68
69
  on_start=on_start,
@@ -87,11 +87,31 @@ async def deserialize(obj: Any, output_schema_name: str | None = None) -> Any:
87
87
 
88
88
  Args:
89
89
  obj: Object to deserialize
90
- output_schema_name: The name of the output schema
90
+ output_schema_name: The name of the output schema (can be
91
+ "list[module.ClassName]" for lists)
91
92
 
92
93
  Returns:
93
94
  Deserialized object
94
95
  """
96
+ # Handle list of Pydantic models (schema format: "list[module.ClassName]")
97
+ if output_schema_name and output_schema_name.startswith("list[") and isinstance(obj, list):
98
+ try:
99
+ # Extract the inner class name from "list[module.ClassName]"
100
+ inner_schema = output_schema_name[5:-1] # Remove "list[" and "]"
101
+ module_path, class_name = inner_schema.rsplit(".", 1)
102
+ module = __import__(module_path, fromlist=[class_name])
103
+ model_class = getattr(module, class_name)
104
+
105
+ # Validate each item in the list back to the Pydantic model
106
+ if issubclass(model_class, BaseModel):
107
+ obj = [model_class.model_validate(item) for item in obj]
108
+ except (ImportError, AttributeError, ValueError, TypeError) as e:
109
+ raise Exception(
110
+ f"Failed to reconstruct Pydantic model list from output_schema_name: "
111
+ f"{output_schema_name}. Error: {str(e)}"
112
+ ) from e
113
+ return obj
114
+
95
115
  # If output_schema_name is present, try to reconstruct the Pydantic model
96
116
  if output_schema_name and isinstance(obj, dict):
97
117
  try: