state-mesh 0.1.0__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 (50) hide show
  1. state_mesh-0.1.0/.gitignore +206 -0
  2. state_mesh-0.1.0/PKG-INFO +88 -0
  3. state_mesh-0.1.0/README.md +50 -0
  4. state_mesh-0.1.0/pyproject.toml +52 -0
  5. state_mesh-0.1.0/requirements.txt +19 -0
  6. state_mesh-0.1.0/state_mesh/__init__.py +26 -0
  7. state_mesh-0.1.0/state_mesh/core/__init__.py +0 -0
  8. state_mesh-0.1.0/state_mesh/core/context.py +122 -0
  9. state_mesh-0.1.0/state_mesh/core/human_input.py +15 -0
  10. state_mesh-0.1.0/state_mesh/core/parallel.py +23 -0
  11. state_mesh-0.1.0/state_mesh/core/pipeline.py +178 -0
  12. state_mesh-0.1.0/state_mesh/core/step.py +116 -0
  13. state_mesh-0.1.0/state_mesh/guardrails/__init__.py +0 -0
  14. state_mesh-0.1.0/state_mesh/guardrails/base.py +20 -0
  15. state_mesh-0.1.0/state_mesh/guardrails/content.py +91 -0
  16. state_mesh-0.1.0/state_mesh/guardrails/runner.py +19 -0
  17. state_mesh-0.1.0/state_mesh/guardrails/schema.py +20 -0
  18. state_mesh-0.1.0/state_mesh/integrations/__init__.py +0 -0
  19. state_mesh-0.1.0/state_mesh/integrations/fastapi.py +111 -0
  20. state_mesh-0.1.0/state_mesh/mcp/__init__.py +0 -0
  21. state_mesh-0.1.0/state_mesh/mcp/bus.py +25 -0
  22. state_mesh-0.1.0/state_mesh/mcp/client.py +47 -0
  23. state_mesh-0.1.0/state_mesh/mcp/registry.py +26 -0
  24. state_mesh-0.1.0/state_mesh/mcp/server_manager.py +59 -0
  25. state_mesh-0.1.0/state_mesh/observability/__init__.py +0 -0
  26. state_mesh-0.1.0/state_mesh/observability/events.py +29 -0
  27. state_mesh-0.1.0/state_mesh/observability/flags.py +11 -0
  28. state_mesh-0.1.0/state_mesh/observability/tracer.py +21 -0
  29. state_mesh-0.1.0/state_mesh/output/__init__.py +0 -0
  30. state_mesh-0.1.0/state_mesh/output/contract.py +14 -0
  31. state_mesh-0.1.0/state_mesh/output/parser.py +91 -0
  32. state_mesh-0.1.0/state_mesh/output/retry.py +38 -0
  33. state_mesh-0.1.0/state_mesh/state/__init__.py +0 -0
  34. state_mesh-0.1.0/state_mesh/state/base.py +19 -0
  35. state_mesh-0.1.0/state_mesh/state/memory.py +19 -0
  36. state_mesh-0.1.0/state_mesh/state/redis_backend.py +31 -0
  37. state_mesh-0.1.0/tests/__init__.py +0 -0
  38. state_mesh-0.1.0/tests/unit/test_context.py +77 -0
  39. state_mesh-0.1.0/tests/unit/test_fastapi.py +187 -0
  40. state_mesh-0.1.0/tests/unit/test_guardrails.py +296 -0
  41. state_mesh-0.1.0/tests/unit/test_human_input.py +192 -0
  42. state_mesh-0.1.0/tests/unit/test_mcp.py +142 -0
  43. state_mesh-0.1.0/tests/unit/test_output.py +227 -0
  44. state_mesh-0.1.0/tests/unit/test_parallel.py +103 -0
  45. state_mesh-0.1.0/tests/unit/test_pipeline.py +135 -0
  46. state_mesh-0.1.0/tests/unit/test_resume.py +213 -0
  47. state_mesh-0.1.0/tests/unit/test_server_manager.py +179 -0
  48. state_mesh-0.1.0/tests/unit/test_sse.py +217 -0
  49. state_mesh-0.1.0/tests/unit/test_state.py +104 -0
  50. state_mesh-0.1.0/tests/unit/test_step.py +102 -0
@@ -0,0 +1,206 @@
1
+ # Created by https://www.toptal.com/developers/gitignore/api/python,react,rust
2
+ # Edit at https://www.toptal.com/developers/gitignore?templates=python,react,rust
3
+
4
+ ### Python ###
5
+ # Byte-compiled / optimized / DLL files
6
+ __pycache__/
7
+ *.py[cod]
8
+ *$py.class
9
+
10
+ # C extensions
11
+ *.so
12
+
13
+ # Distribution / packaging
14
+ .Python
15
+ build/
16
+ develop-eggs/
17
+ dist/
18
+ downloads/
19
+ eggs/
20
+ .eggs/
21
+ lib/
22
+ lib64/
23
+ parts/
24
+ sdist/
25
+ var/
26
+ wheels/
27
+ share/python-wheels/
28
+ *.egg-info/
29
+ .installed.cfg
30
+ *.egg
31
+ MANIFEST
32
+
33
+ # PyInstaller
34
+ # Usually these files are written by a python script from a template
35
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
36
+ *.manifest
37
+ *.spec
38
+
39
+ # Installer logs
40
+ pip-log.txt
41
+ pip-delete-this-directory.txt
42
+
43
+ # Unit test / coverage reports
44
+ htmlcov/
45
+ .tox/
46
+ .nox/
47
+ .coverage
48
+ .coverage.*
49
+ .cache
50
+ nosetests.xml
51
+ coverage.xml
52
+ *.cover
53
+ *.py,cover
54
+ .hypothesis/
55
+ .pytest_cache/
56
+ cover/
57
+
58
+ # Translations
59
+ *.mo
60
+ *.pot
61
+
62
+ # Django stuff:
63
+ *.log
64
+ local_settings.py
65
+ db.sqlite3
66
+ db.sqlite3-journal
67
+
68
+ # Flask stuff:
69
+ instance/
70
+ .webassets-cache
71
+
72
+ # Scrapy stuff:
73
+ .scrapy
74
+
75
+ # Sphinx documentation
76
+ docs/_build/
77
+
78
+ # PyBuilder
79
+ .pybuilder/
80
+ target/
81
+
82
+ # Jupyter Notebook
83
+ .ipynb_checkpoints
84
+
85
+ # IPython
86
+ profile_default/
87
+ ipython_config.py
88
+
89
+ # pyenv
90
+ # For a library or package, you might want to ignore these files since the code is
91
+ # intended to run in multiple environments; otherwise, check them in:
92
+ # .python-version
93
+
94
+ # pipenv
95
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
96
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
97
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
98
+ # install all needed dependencies.
99
+ #Pipfile.lock
100
+
101
+ # poetry
102
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
103
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
104
+ # commonly ignored for libraries.
105
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
106
+ #poetry.lock
107
+
108
+ # pdm
109
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
110
+ #pdm.lock
111
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
112
+ # in version control.
113
+ # https://pdm.fming.dev/#use-with-ide
114
+ .pdm.toml
115
+
116
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
117
+ __pypackages__/
118
+
119
+ # Celery stuff
120
+ celerybeat-schedule
121
+ celerybeat.pid
122
+
123
+ # SageMath parsed files
124
+ *.sage.py
125
+
126
+ # Environments
127
+ .env
128
+ .venv
129
+ env/
130
+ venv/
131
+ ENV/
132
+ env.bak/
133
+ venv.bak/
134
+
135
+ # Spyder project settings
136
+ .spyderproject
137
+ .spyproject
138
+
139
+ # Rope project settings
140
+ .ropeproject
141
+
142
+ # mkdocs documentation
143
+ /site
144
+
145
+ # mypy
146
+ .mypy_cache/
147
+ .dmypy.json
148
+ dmypy.json
149
+
150
+ # Pyre type checker
151
+ .pyre/
152
+
153
+ # pytype static type analyzer
154
+ .pytype/
155
+
156
+ # Cython debug symbols
157
+ cython_debug/
158
+
159
+ # PyCharm
160
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
161
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
162
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
163
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
164
+ #.idea/
165
+
166
+ ### Python Patch ###
167
+ # Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
168
+ poetry.toml
169
+
170
+ # ruff
171
+ .ruff_cache/
172
+
173
+ # LSP config files
174
+ pyrightconfig.json
175
+
176
+ ### react ###
177
+ .DS_*
178
+ logs
179
+ **/*.backup.*
180
+ **/*.back.*
181
+
182
+ node_modules
183
+ bower_components
184
+
185
+ *.sublime*
186
+
187
+ psd
188
+ thumb
189
+ sketch
190
+
191
+ ### Rust ###
192
+ # Generated by Cargo
193
+ # will have compiled files and executables
194
+ debug/
195
+
196
+ # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
197
+ # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
198
+ Cargo.lock
199
+
200
+ # These are backup files generated by rustfmt
201
+ **/*.rs.bk
202
+
203
+ # MSVC Windows builds of rustc generate these, which store debugging information
204
+ *.pdb
205
+
206
+ # End of https://www.toptal.com/developers/gitignore/api/python,react,rust
@@ -0,0 +1,88 @@
1
+ Metadata-Version: 2.4
2
+ Name: state-mesh
3
+ Version: 0.1.0
4
+ Summary: Async-native agentic pipeline runtime for Python backend engineers
5
+ Project-URL: Homepage, https://github.com/statemesh-123/state-mesh-agent-runtime
6
+ Project-URL: Repository, https://github.com/statemesh-123/state-mesh-agent-runtime
7
+ Project-URL: Issues, https://github.com/statemesh-123/state-mesh-agent-runtime/issues
8
+ Author: Meghala
9
+ Author-email: Jayaprabha <statemesh@gmail.com>
10
+ License: MIT
11
+ Keywords: agent,async,llm,pipeline,runtime
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Topic :: Software Development :: Libraries
18
+ Requires-Python: >=3.11
19
+ Requires-Dist: anyio>=4.0
20
+ Requires-Dist: fastapi>=0.100
21
+ Requires-Dist: mcp>=1.0
22
+ Requires-Dist: pydantic>=2.0
23
+ Provides-Extra: dev
24
+ Requires-Dist: httpx; extra == 'dev'
25
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
26
+ Requires-Dist: pytest>=8.0; extra == 'dev'
27
+ Requires-Dist: ruff; extra == 'dev'
28
+ Provides-Extra: otel
29
+ Requires-Dist: opentelemetry-api>=1.20; extra == 'otel'
30
+ Requires-Dist: opentelemetry-sdk>=1.20; extra == 'otel'
31
+ Provides-Extra: postgres
32
+ Requires-Dist: asyncpg>=0.29; extra == 'postgres'
33
+ Provides-Extra: redis
34
+ Requires-Dist: redis>=5.0; extra == 'redis'
35
+ Provides-Extra: toxicity
36
+ Requires-Dist: detoxify>=0.5; extra == 'toxicity'
37
+ Description-Content-Type: text/markdown
38
+
39
+ # State-Mesh
40
+
41
+ Async-native agentic pipeline runtime for Python backend engineers.
42
+
43
+ ## Why State-Mesh
44
+
45
+ - **Typed state** — Pydantic models flow through every step, no silent dict mutations
46
+ - **Built-in MCP** — register MCP servers once, every step can call any tool
47
+ - **Production-ready** — guardrails, structured output retry, OTel tracing, pipeline resumption out of the box
48
+
49
+ ## Features
50
+
51
+ - Async-first step execution with retry, timeout, and branching
52
+ - Parallel step execution with `asyncio.gather`
53
+ - Structured output contracts with automatic LLM retry
54
+ - Guard chain — schema, PII, confidence, toxicity guards built in
55
+ - MCP tool bus (SSE + stdio transport)
56
+ - OpenTelemetry tracing with swappable exporters
57
+ - Redis state backend with pipeline resumption
58
+ - FastAPI integration — `mount_pipeline`, `stream_pipeline`, `pipeline_router`
59
+ - Human-in-the-loop with timeout and fallback
60
+
61
+ ## Install
62
+
63
+ ```bash
64
+ pip install state-mesh
65
+ pip install "state-mesh[redis]" # Redis backend
66
+ pip install "state-mesh[otel]" # OpenTelemetry
67
+ ```
68
+
69
+ ## Quickstart
70
+
71
+ ```python
72
+ from state_mesh import step, Pipeline, Context, OutputContract
73
+ from pydantic import BaseModel
74
+
75
+ class Result(BaseModel):
76
+ summary: str
77
+
78
+ @step(output_contract=OutputContract(target_schema=Result))
79
+ async def summarise(prompt: str) -> str:
80
+ return await my_llm(prompt)
81
+
82
+ pipeline = Pipeline(steps=[summarise], name="summarise")
83
+ result = await pipeline.run(Context(state={}))
84
+ ```
85
+
86
+ ## License
87
+
88
+ MIT
@@ -0,0 +1,50 @@
1
+ # State-Mesh
2
+
3
+ Async-native agentic pipeline runtime for Python backend engineers.
4
+
5
+ ## Why State-Mesh
6
+
7
+ - **Typed state** — Pydantic models flow through every step, no silent dict mutations
8
+ - **Built-in MCP** — register MCP servers once, every step can call any tool
9
+ - **Production-ready** — guardrails, structured output retry, OTel tracing, pipeline resumption out of the box
10
+
11
+ ## Features
12
+
13
+ - Async-first step execution with retry, timeout, and branching
14
+ - Parallel step execution with `asyncio.gather`
15
+ - Structured output contracts with automatic LLM retry
16
+ - Guard chain — schema, PII, confidence, toxicity guards built in
17
+ - MCP tool bus (SSE + stdio transport)
18
+ - OpenTelemetry tracing with swappable exporters
19
+ - Redis state backend with pipeline resumption
20
+ - FastAPI integration — `mount_pipeline`, `stream_pipeline`, `pipeline_router`
21
+ - Human-in-the-loop with timeout and fallback
22
+
23
+ ## Install
24
+
25
+ ```bash
26
+ pip install state-mesh
27
+ pip install "state-mesh[redis]" # Redis backend
28
+ pip install "state-mesh[otel]" # OpenTelemetry
29
+ ```
30
+
31
+ ## Quickstart
32
+
33
+ ```python
34
+ from state_mesh import step, Pipeline, Context, OutputContract
35
+ from pydantic import BaseModel
36
+
37
+ class Result(BaseModel):
38
+ summary: str
39
+
40
+ @step(output_contract=OutputContract(target_schema=Result))
41
+ async def summarise(prompt: str) -> str:
42
+ return await my_llm(prompt)
43
+
44
+ pipeline = Pipeline(steps=[summarise], name="summarise")
45
+ result = await pipeline.run(Context(state={}))
46
+ ```
47
+
48
+ ## License
49
+
50
+ MIT
@@ -0,0 +1,52 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "state-mesh"
7
+ version = "0.1.0"
8
+ description = "Async-native agentic pipeline runtime for Python backend engineers"
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ requires-python = ">=3.11"
12
+ authors = [
13
+ { name = "Jayaprabha", email = "statemesh@gmail.com" },
14
+ { name = "Meghala" },
15
+ ]
16
+ keywords = ["agent", "pipeline", "llm", "async", "runtime"]
17
+ classifiers = [
18
+ "Development Status :: 3 - Alpha",
19
+ "Intended Audience :: Developers",
20
+ "License :: OSI Approved :: MIT License",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Topic :: Software Development :: Libraries",
24
+ ]
25
+
26
+ dependencies = [
27
+ "pydantic>=2.0",
28
+ "anyio>=4.0",
29
+ "fastapi>=0.100",
30
+ "mcp>=1.0",
31
+ ]
32
+
33
+ [project.urls]
34
+ Homepage = "https://github.com/statemesh-123/state-mesh-agent-runtime"
35
+ Repository = "https://github.com/statemesh-123/state-mesh-agent-runtime"
36
+ Issues = "https://github.com/statemesh-123/state-mesh-agent-runtime/issues"
37
+
38
+ [project.optional-dependencies]
39
+ redis = ["redis>=5.0"]
40
+ postgres = ["asyncpg>=0.29"]
41
+ otel = ["opentelemetry-api>=1.20", "opentelemetry-sdk>=1.20"]
42
+ toxicity = ["detoxify>=0.5"]
43
+ dev = ["pytest>=8.0", "pytest-asyncio>=0.23", "ruff", "httpx"]
44
+
45
+ [tool.hatch.build.targets.wheel]
46
+ packages = ["state_mesh"]
47
+
48
+ [tool.pytest.ini_options]
49
+ asyncio_mode = "auto"
50
+
51
+ [tool.ruff]
52
+ line-length = 88
@@ -0,0 +1,19 @@
1
+ annotated-types==0.7.0
2
+ colorama==0.4.6
3
+ iniconfig==2.3.0
4
+ packaging==26.2
5
+ pluggy==1.6.0
6
+ pydantic==2.13.4
7
+ pydantic_core==2.46.4
8
+ Pygments==2.20.0
9
+ pytest==9.1.0
10
+ typing-inspection==0.4.2
11
+ typing_extensions==4.15.0
12
+ pytest-asyncio
13
+ opentelemetry-api
14
+ opentelemetry-sdk
15
+ fastapi
16
+ mcp
17
+ redis
18
+ detoxify
19
+ httpx2
@@ -0,0 +1,26 @@
1
+ from state_mesh.core.context import Context, Flag
2
+ from state_mesh.core.step import Step, Branch, RetryConfig, step
3
+ from state_mesh.core.pipeline import Pipeline, PipelineResult
4
+ from state_mesh.guardrails.base import Guard, GuardResult
5
+ from state_mesh.guardrails.schema import SchemaGuard
6
+ from state_mesh.guardrails.content import PIIGuard
7
+ from state_mesh.output.contract import OutputContract
8
+ from state_mesh.mcp.server_manager import MCPServerConfig, MCPServerManager
9
+
10
+ __all__ = [
11
+ "step",
12
+ "Step",
13
+ "Pipeline",
14
+ "Context",
15
+ "RetryConfig",
16
+ "Branch",
17
+ "PipelineResult",
18
+ "Guard",
19
+ "GuardResult",
20
+ "SchemaGuard",
21
+ "PIIGuard",
22
+ "OutputContract",
23
+ "Flag",
24
+ "MCPServerConfig",
25
+ "MCPServerManager",
26
+ ]
File without changes
@@ -0,0 +1,122 @@
1
+ from __future__ import annotations
2
+
3
+ import uuid
4
+ from datetime import datetime, timezone
5
+ from typing import Any, Generic, TypeVar, Literal, TYPE_CHECKING
6
+ from pydantic import BaseModel, Field
7
+
8
+ if TYPE_CHECKING:
9
+ from bus import MCPBus
10
+
11
+ StateSchema = TypeVar("StateSchema", bound=BaseModel)
12
+
13
+
14
+ class Flag(BaseModel):
15
+ step_name:str
16
+ severity: Literal["info","warn","error"]
17
+ timestamp:datetime=Field(default_factory=lambda:datetime.now(timezone.utc))
18
+ flag_type: str
19
+ reason:str
20
+ payload:dict[str,Any]=Field(default_factory=dict)
21
+
22
+ class ContextMutationError(Exception):
23
+ pass
24
+
25
+ class Context(Generic[StateSchema]):
26
+ """
27
+ Context is the main object that is passed to all steps.
28
+ It contains the state, run_id, trace_id, pipeline_name and flags.
29
+ """
30
+ def __init__(self,state,run_id=None,trace_id=None,pipeline_name="unnamed",tools:MCPBus|None=None):
31
+ self._state = state
32
+ self._run_id = run_id or str(uuid.uuid4())
33
+ self._trace_id = trace_id or str(uuid.uuid4())
34
+ self._pipeline_name = pipeline_name
35
+ self._flags:list[Flag] = []
36
+ self._extras:dict[str,Any]={}
37
+ self._tools=tools
38
+ self._step_name=None
39
+
40
+ @property
41
+ def state(self) -> StateSchema:
42
+ return self._state
43
+
44
+ @property
45
+ def tools(self) -> MCPBus|None:
46
+ return self._tools
47
+
48
+ @property
49
+ def run_id(self) -> str:
50
+ return self._run_id
51
+
52
+ @property
53
+ def trace_id(self) -> str:
54
+ return self._trace_id
55
+
56
+ @property
57
+ def pipeline_name(self) -> str:
58
+ return self._pipeline_name
59
+
60
+ @property
61
+ def flags(self) -> list[Flag]:
62
+ flag=self._flags.copy() # sp the user wont be able directly modify this
63
+ return flag
64
+
65
+ def set(self,key:str,value:Any)->None:
66
+ if key in self._extras:
67
+ raise ContextMutationError(f"Key {key} already exists in extras")
68
+ self._extras[key]=value
69
+
70
+ def get(self,key:str,default:Any=None)->Any:
71
+ return self._extras.get(key,default)
72
+
73
+ def emit_flag(self,flag_type:str,reason:str,severity:Literal["info","warn","error"]="warn",payload:dict[str,Any]|None=None)->None:
74
+ self._flags.append(Flag(
75
+ step_name=self._step_name,
76
+ severity=severity,
77
+ flag_type=flag_type,
78
+ reason=reason,
79
+ payload=payload or {}
80
+ ))
81
+
82
+ def _set_current_step(self, step_name: str) -> None:
83
+ self._step_name = step_name
84
+
85
+ def _replace_state(self, new_state: StateSchema) -> None:
86
+ self._state = new_state
87
+
88
+ #serialize the context for pipeline resumption
89
+ def to_dict(self) -> dict[str, Any]:
90
+ return {
91
+ "state": self._state.model_dump() if isinstance(self._state, BaseModel) else self._state,
92
+ "run_id": self._run_id,
93
+ "trace_id": self._trace_id,
94
+ "pipeline_name": self._pipeline_name,
95
+ "extras": self._extras,
96
+ "flags": [f.model_dump() for f in self._flags],
97
+ "last_completed_step": self._step_name,
98
+ }
99
+
100
+ @classmethod
101
+ def from_dict(cls, data: dict[str, Any], state_schema: type[BaseModel]) -> Context:
102
+ ctx = cls(
103
+ state=state_schema(**data["state"]),
104
+ run_id=data["run_id"],
105
+ trace_id=data["trace_id"],
106
+ pipeline_name=data["pipeline_name"],
107
+ )
108
+ ctx._extras = data["extras"]
109
+ ctx._flags = [Flag(**f) for f in data["flags"]]
110
+ ctx._step_name = data.get("last_completed_step")
111
+ return ctx
112
+
113
+ def __repr__(self) -> str:
114
+ return (
115
+ f"Context(run_id={self._run_id!r}, "
116
+ f"step={self._step_name!r}, "
117
+ f"flags={len(self._flags)})"
118
+ )
119
+
120
+
121
+
122
+
@@ -0,0 +1,15 @@
1
+ from __future__ import annotations
2
+
3
+
4
+ class HumanInput:
5
+ def __init__(
6
+ self,
7
+ prompt: str,
8
+ response_key: str,
9
+ timeout_seconds: float = 300.0,
10
+ fallback_step: str | None = None,
11
+ ):
12
+ self.prompt = prompt
13
+ self.response_key = response_key
14
+ self.timeout_seconds = timeout_seconds
15
+ self.fallback_step = fallback_step
@@ -0,0 +1,23 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+
5
+ from state_mesh.core.context import Context
6
+ from state_mesh.core.step import Step, StepResult
7
+
8
+
9
+ class Parallel:
10
+ def __init__(self, steps: list[Step], name: str = "parallel"):
11
+ self.name = name
12
+ self.steps = steps
13
+
14
+ async def execute(self, ctx: Context) -> list[StepResult]:
15
+ results: list[StepResult] = list(
16
+ await asyncio.gather(*[step.execute(ctx) for step in self.steps], return_exceptions=True)
17
+ )
18
+
19
+ for step, result in zip(self.steps, results):
20
+ if not isinstance(result, BaseException) and result.status == "success":
21
+ ctx.set(step.name, result.output)
22
+
23
+ return results