dissyslab 1.0.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 (98) hide show
  1. dissyslab-1.0.2/LICENSE +15 -0
  2. dissyslab-1.0.2/PKG-INFO +188 -0
  3. dissyslab-1.0.2/README.md +142 -0
  4. dissyslab-1.0.2/dissyslab/__init__.py +25 -0
  5. dissyslab-1.0.2/dissyslab/blocks/__init__.py +32 -0
  6. dissyslab-1.0.2/dissyslab/blocks/fanin.py +90 -0
  7. dissyslab-1.0.2/dissyslab/blocks/fanout.py +69 -0
  8. dissyslab-1.0.2/dissyslab/blocks/merge_synch.py +73 -0
  9. dissyslab-1.0.2/dissyslab/blocks/role.py +113 -0
  10. dissyslab-1.0.2/dissyslab/blocks/sink.py +77 -0
  11. dissyslab-1.0.2/dissyslab/blocks/source.py +119 -0
  12. dissyslab-1.0.2/dissyslab/blocks/split.py +110 -0
  13. dissyslab-1.0.2/dissyslab/blocks/transform.py +82 -0
  14. dissyslab-1.0.2/dissyslab/builder.py +222 -0
  15. dissyslab-1.0.2/dissyslab/cli.py +239 -0
  16. dissyslab-1.0.2/dissyslab/components/__init__.py +24 -0
  17. dissyslab-1.0.2/dissyslab/components/sinks/__init__.py +13 -0
  18. dissyslab-1.0.2/dissyslab/components/sinks/console_display.py +66 -0
  19. dissyslab-1.0.2/dissyslab/components/sinks/demo_email_alerter.py +98 -0
  20. dissyslab-1.0.2/dissyslab/components/sinks/discard.py +28 -0
  21. dissyslab-1.0.2/dissyslab/components/sinks/file_system.py +70 -0
  22. dissyslab-1.0.2/dissyslab/components/sinks/gmail_sink.py +135 -0
  23. dissyslab-1.0.2/dissyslab/components/sinks/intelligence_display.py +123 -0
  24. dissyslab-1.0.2/dissyslab/components/sinks/llm_builders.py +36 -0
  25. dissyslab-1.0.2/dissyslab/components/sinks/mcp_sink.py +204 -0
  26. dissyslab-1.0.2/dissyslab/components/sinks/photo_dashboard.py +222 -0
  27. dissyslab-1.0.2/dissyslab/components/sinks/replay_csv_in.py +72 -0
  28. dissyslab-1.0.2/dissyslab/components/sinks/rl_dashboard.py +165 -0
  29. dissyslab-1.0.2/dissyslab/components/sinks/sine_mixture_source.py +160 -0
  30. dissyslab-1.0.2/dissyslab/components/sinks/sink_jsonl_recorder.py +58 -0
  31. dissyslab-1.0.2/dissyslab/components/sinks/sink_list_collector.py +33 -0
  32. dissyslab-1.0.2/dissyslab/components/sinks/sink_simple_file.py +38 -0
  33. dissyslab-1.0.2/dissyslab/components/sinks/test_llm_enricher.py +129 -0
  34. dissyslab-1.0.2/dissyslab/components/sinks/test_webhook.py +14 -0
  35. dissyslab-1.0.2/dissyslab/components/sinks/webhook_sink.py +186 -0
  36. dissyslab-1.0.2/dissyslab/components/sources/__init__.py +16 -0
  37. dissyslab-1.0.2/dissyslab/components/sources/bluesky_jetstream_source.py +235 -0
  38. dissyslab-1.0.2/dissyslab/components/sources/calendar_source.py +290 -0
  39. dissyslab-1.0.2/dissyslab/components/sources/cartpole_source.py +238 -0
  40. dissyslab-1.0.2/dissyslab/components/sources/clock_source.py +96 -0
  41. dissyslab-1.0.2/dissyslab/components/sources/demo_bluesky_jetstream.py +401 -0
  42. dissyslab-1.0.2/dissyslab/components/sources/demo_job_source.py +42 -0
  43. dissyslab-1.0.2/dissyslab/components/sources/demo_rss_source.py +292 -0
  44. dissyslab-1.0.2/dissyslab/components/sources/file_source.py +273 -0
  45. dissyslab-1.0.2/dissyslab/components/sources/generated/__init__.py +0 -0
  46. dissyslab-1.0.2/dissyslab/components/sources/generated/stocks_source.py +45 -0
  47. dissyslab-1.0.2/dissyslab/components/sources/generated/weather_source.py +111 -0
  48. dissyslab-1.0.2/dissyslab/components/sources/gmail_source.py +249 -0
  49. dissyslab-1.0.2/dissyslab/components/sources/image_folder_source.py +147 -0
  50. dissyslab-1.0.2/dissyslab/components/sources/list_source.py +30 -0
  51. dissyslab-1.0.2/dissyslab/components/sources/mcp_source.py +272 -0
  52. dissyslab-1.0.2/dissyslab/components/sources/natural_numbers_source.py +40 -0
  53. dissyslab-1.0.2/dissyslab/components/sources/rss_normalizer.py +328 -0
  54. dissyslab-1.0.2/dissyslab/components/sources/rss_source.py +196 -0
  55. dissyslab-1.0.2/dissyslab/components/sources/test_bluesky.py +16 -0
  56. dissyslab-1.0.2/dissyslab/components/sources/web_scraper.py +463 -0
  57. dissyslab-1.0.2/dissyslab/components/transformers/__init__.py +17 -0
  58. dissyslab-1.0.2/dissyslab/components/transformers/ai_agent.py +154 -0
  59. dissyslab-1.0.2/dissyslab/components/transformers/composition_analyzer.py +199 -0
  60. dissyslab-1.0.2/dissyslab/components/transformers/demo_ai_agent.py +230 -0
  61. dissyslab-1.0.2/dissyslab/components/transformers/demo_jobs.py +141 -0
  62. dissyslab-1.0.2/dissyslab/components/transformers/demo_salary.py +122 -0
  63. dissyslab-1.0.2/dissyslab/components/transformers/demo_sentiment.py +102 -0
  64. dissyslab-1.0.2/dissyslab/components/transformers/demo_spam.py +140 -0
  65. dissyslab-1.0.2/dissyslab/components/transformers/demo_topic.py +160 -0
  66. dissyslab-1.0.2/dissyslab/components/transformers/demo_urgency.py +149 -0
  67. dissyslab-1.0.2/dissyslab/components/transformers/exposure_analyzer.py +177 -0
  68. dissyslab-1.0.2/dissyslab/components/transformers/learning_curve_analyzer.py +187 -0
  69. dissyslab-1.0.2/dissyslab/components/transformers/policy_analyzer.py +168 -0
  70. dissyslab-1.0.2/dissyslab/components/transformers/prompts.py +563 -0
  71. dissyslab-1.0.2/dissyslab/components/transformers/reward_analyzer.py +141 -0
  72. dissyslab-1.0.2/dissyslab/components/transformers/sharpness_analyzer.py +149 -0
  73. dissyslab-1.0.2/dissyslab/components/transformers/stateful_agent.py +170 -0
  74. dissyslab-1.0.2/dissyslab/composed_agent.py +212 -0
  75. dissyslab-1.0.2/dissyslab/core.py +320 -0
  76. dissyslab-1.0.2/dissyslab/network.py +759 -0
  77. dissyslab-1.0.2/dissyslab/office/__init__.py +17 -0
  78. dissyslab-1.0.2/dissyslab/office/make_network.py +368 -0
  79. dissyslab-1.0.2/dissyslab/office/make_office.py +296 -0
  80. dissyslab-1.0.2/dissyslab/office/office_compiler.py +230 -0
  81. dissyslab-1.0.2/dissyslab/office/utils.py +916 -0
  82. dissyslab-1.0.2/dissyslab/os_agent.py +186 -0
  83. dissyslab-1.0.2/dissyslab/py.typed +0 -0
  84. dissyslab-1.0.2/dissyslab/utils/__init__.py +31 -0
  85. dissyslab-1.0.2/dissyslab/utils/get_credentials.py +172 -0
  86. dissyslab-1.0.2/dissyslab/utils/visualize.py +491 -0
  87. dissyslab-1.0.2/dissyslab.egg-info/PKG-INFO +188 -0
  88. dissyslab-1.0.2/dissyslab.egg-info/SOURCES.txt +96 -0
  89. dissyslab-1.0.2/dissyslab.egg-info/dependency_links.txt +1 -0
  90. dissyslab-1.0.2/dissyslab.egg-info/entry_points.txt +2 -0
  91. dissyslab-1.0.2/dissyslab.egg-info/requires.txt +18 -0
  92. dissyslab-1.0.2/dissyslab.egg-info/top_level.txt +1 -0
  93. dissyslab-1.0.2/pyproject.toml +109 -0
  94. dissyslab-1.0.2/setup.cfg +4 -0
  95. dissyslab-1.0.2/tests/test_compositionality.py +100 -0
  96. dissyslab-1.0.2/tests/test_compositionality_double.py +154 -0
  97. dissyslab-1.0.2/tests/test_mcp_source.py +37 -0
  98. dissyslab-1.0.2/tests/test_termination.py +101 -0
@@ -0,0 +1,15 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 K. Mani Chandy
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND...
@@ -0,0 +1,188 @@
1
+ Metadata-Version: 2.4
2
+ Name: dissyslab
3
+ Version: 1.0.2
4
+ Summary: DisSysLab — build continuous offices of AI agents in plain English.
5
+ Author-email: "K. Mani Chandy" <kmchandy@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/kmchandy/DisSysLab
8
+ Project-URL: Source, https://github.com/kmchandy/DisSysLab
9
+ Project-URL: Documentation, https://github.com/kmchandy/DisSysLab#readme
10
+ Project-URL: Issues, https://github.com/kmchandy/DisSysLab/issues
11
+ Keywords: distributed-systems,agents,ai,education,claude,anthropic,llm,dataflow
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Education
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.9
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Topic :: Education
23
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
24
+ Classifier: Topic :: System :: Distributed Computing
25
+ Requires-Python: >=3.9
26
+ Description-Content-Type: text/markdown
27
+ License-File: LICENSE
28
+ Requires-Dist: anthropic>=0.39.0
29
+ Requires-Dist: python-dotenv>=1.0.0
30
+ Requires-Dist: feedparser>=6.0.11
31
+ Requires-Dist: requests>=2.31.0
32
+ Requires-Dist: websocket-client>=1.6.0
33
+ Requires-Dist: beautifulsoup4>=4.12.0
34
+ Requires-Dist: Pillow>=10.0.0
35
+ Requires-Dist: numpy>=1.24.0
36
+ Requires-Dist: scipy>=1.11.0
37
+ Provides-Extra: dev
38
+ Requires-Dist: pytest>=8.0.0; extra == "dev"
39
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
40
+ Requires-Dist: pytest-timeout>=2.1.0; extra == "dev"
41
+ Requires-Dist: ruff>=0.6.9; extra == "dev"
42
+ Requires-Dist: black>=24.8.0; extra == "dev"
43
+ Requires-Dist: build>=1.2.1; extra == "dev"
44
+ Requires-Dist: twine>=5.1.1; extra == "dev"
45
+ Dynamic: license-file
46
+
47
+ # DSL — Build Your Own Office of AI Agents
48
+
49
+ **AI tools answer when you ask. What if a network of AI agents worked for you all the time?**
50
+
51
+ ## 🎓 New to DisSysLab? Start here
52
+ [Take the 5-minute micro-course](https://kmchandy.github.io/DisSysLab/office_microcourse.html)
53
+
54
+ Then read [Getting started](GETTING_STARTED.md)
55
+
56
+ ---
57
+
58
+ DSL lets you build an office of AI agents that runs continuously and never stops
59
+ until you tell it to.
60
+ Your agents monitor sources, analyze data, control devices, and store results for you, all
61
+ the time.
62
+
63
+ ---
64
+
65
+ ![Situation Room](gallery/org_situation_room/screenshot.png)
66
+
67
+ *A Situation Room scanning live news and social media in real time.
68
+ You didn't write any code. You wrote two plain English documents.*
69
+
70
+ ---
71
+
72
+ ## Get Started in 3 Commands
73
+
74
+ ```bash
75
+ git clone https://github.com/kmchandy/DisSysLab.git && cd DisSysLab
76
+ pip install -e .
77
+ export ANTHROPIC_API_KEY='your-key'
78
+
79
+ dsl run gallery/org_intelligence_briefing/
80
+ ```
81
+
82
+ `pip install -e .` installs DisSysLab in editable mode and puts the
83
+ `dsl` command on your PATH. `dsl run` reads your plain English files,
84
+ shows you the routing, asks "Does this look right?", and starts your
85
+ office. Run `dsl doctor` first if you want to check your setup, or
86
+ `dsl gallery` to browse the offices that ship with the repo.
87
+
88
+ ---
89
+
90
+ ## Two Paths Forward
91
+
92
+ ### Path A — Describe your office in plain English: Job Descriptions and Org Chart
93
+
94
+ You write job descriptions and an org chart. No programming required.
95
+
96
+ **The job descriptions — what each agent does:**
97
+
98
+ ```
99
+ # Role: analyst
100
+
101
+ You are a news analyst who receives posts and articles and sends
102
+ items to an editor or a discard.
103
+
104
+ Your job is to decide if each item is relevant to significant
105
+ political developments or economic events — specifically involving
106
+ topics such as Trump, Congress, Senate, elections, the Federal Reserve,
107
+ tariffs, inflation, markets, Ukraine, Iran, trade policy, or the
108
+ broader economy.
109
+
110
+ Exclude celebrity gossip, sports, entertainment, and personal
111
+ opinions with no broader political or economic significance.
112
+
113
+ If the item is relevant, send to editor.
114
+ Otherwise send to discard.
115
+ ```
116
+
117
+ ```
118
+ # Role: editor
119
+
120
+ You are a senior editor who receives posts and articles and sends
121
+ items to a situation_room.
122
+
123
+ Your job is to rate each item for its significance — CRITICAL, HIGH,
124
+ MEDIUM, or LOW — and rewrite it as a crisp one-paragraph briefing note.
125
+ Note whether the item came from social media or news. Preserve the
126
+ source, url, timestamp, and author fields. Put your significance rating
127
+ in a field called "significance" and your summary in the "text" field.
128
+
129
+ Always send results to situation_room.
130
+ ```
131
+
132
+ **The org chart — who connects to whom:**
133
+
134
+ ```
135
+ Sources: bluesky(max_posts=None, lifetime=None),
136
+ al_jazeera(max_articles=10, poll_interval=600),
137
+ bbc_world(max_articles=10, poll_interval=600)
138
+ Sinks: intelligence_display(max_items=8),
139
+ jsonl_recorder(path="situation_room.jsonl")
140
+
141
+ Agents:
142
+ Alex is an analyst.
143
+ Morgan is an editor.
144
+
145
+ Connections:
146
+ bluesky's destination is Alex.
147
+ al_jazeera's destination is Alex.
148
+ bbc_world's destination is Alex.
149
+ Alex's editor is Morgan.
150
+ Alex's discard is jsonl_recorder.
151
+ Morgan's situation_room are intelligence_display and jsonl_recorder.
152
+ ```
153
+
154
+ That's it. Change the topics, change the agents, change the sources.
155
+ The office is yours.
156
+
157
+ **Offices can contain offices.** An office is
158
+ a building block which you can put into a network with other offices
159
+ — a news office feeding a strategy office, a research
160
+ office feeding an editorial office. Each office is a black box: the
161
+ organization only knows what goes in and what comes out. You can build
162
+ organizations of arbitrary complexity, one office at a time, reusing
163
+ offices across different networks.
164
+
165
+ **→ [Go to the gallery](gallery/README.md) to run an existing office and build your own.**
166
+
167
+ ---
168
+
169
+ ### Path B — Learn how distributed systems work
170
+
171
+ **Interested in how DSL works under the hood?**
172
+ DSL is also a Python framework for building distributed systems —
173
+ concurrent agents, message queues, routing, and termination detection.
174
+ See [`examples/`](examples/README.md) for a module sequence that takes you
175
+ from your first network to building distributed systems from scratch.
176
+
177
+ ---
178
+
179
+ ## Requirements
180
+
181
+ - Python 3.9+
182
+ - Anthropic API key ([get one here](https://console.anthropic.com))
183
+ - Install with `pip install -e .` from this repo (or `pip install -r requirements.txt` if you only want the dependencies without the `dsl` command)
184
+
185
+ ---
186
+
187
+ *DSL is an open research project exploring natural language interfaces
188
+ to persistent distributed systems.*
@@ -0,0 +1,142 @@
1
+ # DSL — Build Your Own Office of AI Agents
2
+
3
+ **AI tools answer when you ask. What if a network of AI agents worked for you all the time?**
4
+
5
+ ## 🎓 New to DisSysLab? Start here
6
+ [Take the 5-minute micro-course](https://kmchandy.github.io/DisSysLab/office_microcourse.html)
7
+
8
+ Then read [Getting started](GETTING_STARTED.md)
9
+
10
+ ---
11
+
12
+ DSL lets you build an office of AI agents that runs continuously and never stops
13
+ until you tell it to.
14
+ Your agents monitor sources, analyze data, control devices, and store results for you, all
15
+ the time.
16
+
17
+ ---
18
+
19
+ ![Situation Room](gallery/org_situation_room/screenshot.png)
20
+
21
+ *A Situation Room scanning live news and social media in real time.
22
+ You didn't write any code. You wrote two plain English documents.*
23
+
24
+ ---
25
+
26
+ ## Get Started in 3 Commands
27
+
28
+ ```bash
29
+ git clone https://github.com/kmchandy/DisSysLab.git && cd DisSysLab
30
+ pip install -e .
31
+ export ANTHROPIC_API_KEY='your-key'
32
+
33
+ dsl run gallery/org_intelligence_briefing/
34
+ ```
35
+
36
+ `pip install -e .` installs DisSysLab in editable mode and puts the
37
+ `dsl` command on your PATH. `dsl run` reads your plain English files,
38
+ shows you the routing, asks "Does this look right?", and starts your
39
+ office. Run `dsl doctor` first if you want to check your setup, or
40
+ `dsl gallery` to browse the offices that ship with the repo.
41
+
42
+ ---
43
+
44
+ ## Two Paths Forward
45
+
46
+ ### Path A — Describe your office in plain English: Job Descriptions and Org Chart
47
+
48
+ You write job descriptions and an org chart. No programming required.
49
+
50
+ **The job descriptions — what each agent does:**
51
+
52
+ ```
53
+ # Role: analyst
54
+
55
+ You are a news analyst who receives posts and articles and sends
56
+ items to an editor or a discard.
57
+
58
+ Your job is to decide if each item is relevant to significant
59
+ political developments or economic events — specifically involving
60
+ topics such as Trump, Congress, Senate, elections, the Federal Reserve,
61
+ tariffs, inflation, markets, Ukraine, Iran, trade policy, or the
62
+ broader economy.
63
+
64
+ Exclude celebrity gossip, sports, entertainment, and personal
65
+ opinions with no broader political or economic significance.
66
+
67
+ If the item is relevant, send to editor.
68
+ Otherwise send to discard.
69
+ ```
70
+
71
+ ```
72
+ # Role: editor
73
+
74
+ You are a senior editor who receives posts and articles and sends
75
+ items to a situation_room.
76
+
77
+ Your job is to rate each item for its significance — CRITICAL, HIGH,
78
+ MEDIUM, or LOW — and rewrite it as a crisp one-paragraph briefing note.
79
+ Note whether the item came from social media or news. Preserve the
80
+ source, url, timestamp, and author fields. Put your significance rating
81
+ in a field called "significance" and your summary in the "text" field.
82
+
83
+ Always send results to situation_room.
84
+ ```
85
+
86
+ **The org chart — who connects to whom:**
87
+
88
+ ```
89
+ Sources: bluesky(max_posts=None, lifetime=None),
90
+ al_jazeera(max_articles=10, poll_interval=600),
91
+ bbc_world(max_articles=10, poll_interval=600)
92
+ Sinks: intelligence_display(max_items=8),
93
+ jsonl_recorder(path="situation_room.jsonl")
94
+
95
+ Agents:
96
+ Alex is an analyst.
97
+ Morgan is an editor.
98
+
99
+ Connections:
100
+ bluesky's destination is Alex.
101
+ al_jazeera's destination is Alex.
102
+ bbc_world's destination is Alex.
103
+ Alex's editor is Morgan.
104
+ Alex's discard is jsonl_recorder.
105
+ Morgan's situation_room are intelligence_display and jsonl_recorder.
106
+ ```
107
+
108
+ That's it. Change the topics, change the agents, change the sources.
109
+ The office is yours.
110
+
111
+ **Offices can contain offices.** An office is
112
+ a building block which you can put into a network with other offices
113
+ — a news office feeding a strategy office, a research
114
+ office feeding an editorial office. Each office is a black box: the
115
+ organization only knows what goes in and what comes out. You can build
116
+ organizations of arbitrary complexity, one office at a time, reusing
117
+ offices across different networks.
118
+
119
+ **→ [Go to the gallery](gallery/README.md) to run an existing office and build your own.**
120
+
121
+ ---
122
+
123
+ ### Path B — Learn how distributed systems work
124
+
125
+ **Interested in how DSL works under the hood?**
126
+ DSL is also a Python framework for building distributed systems —
127
+ concurrent agents, message queues, routing, and termination detection.
128
+ See [`examples/`](examples/README.md) for a module sequence that takes you
129
+ from your first network to building distributed systems from scratch.
130
+
131
+ ---
132
+
133
+ ## Requirements
134
+
135
+ - Python 3.9+
136
+ - Anthropic API key ([get one here](https://console.anthropic.com))
137
+ - Install with `pip install -e .` from this repo (or `pip install -r requirements.txt` if you only want the dependencies without the `dsl` command)
138
+
139
+ ---
140
+
141
+ *DSL is an open research project exploring natural language interfaces
142
+ to persistent distributed systems.*
@@ -0,0 +1,25 @@
1
+ # dissyslab/__init__.py
2
+ """
3
+ DisSysLab - Distributed Systems Teaching Framework
4
+
5
+ Public API exports.
6
+ """
7
+
8
+ from dissyslab.core import Agent, ExceptionThread
9
+ from dissyslab.network import Network
10
+ from dissyslab.builder import network, PortReference
11
+
12
+ __all__ = [
13
+ # Core
14
+ 'Agent',
15
+ 'ExceptionThread',
16
+
17
+ # Network
18
+ 'Network',
19
+ 'network',
20
+
21
+ # Builder
22
+ 'PortReference',
23
+ ]
24
+
25
+ __version__ = '1.0.2'
@@ -0,0 +1,32 @@
1
+ # dissyslab/blocks/__init__.py
2
+ """
3
+ Standard agent blocks for building distributed systems.
4
+
5
+ Available blocks:
6
+ - Source: Generate messages (no inputs)
7
+ - Transform: Process messages (one input, one output)
8
+ - Sink: Consume messages (one input, no outputs)
9
+ - Broadcast: Fanout - copy message to multiple outputs
10
+ - MergeAsynch: Fanin - merge multiple inputs into one stream
11
+ - Split: Content-based routing to multiple outputs
12
+ """
13
+
14
+ from dissyslab.blocks.source import Source
15
+ from dissyslab.blocks.transform import Transform
16
+ from dissyslab.blocks.sink import Sink
17
+ from dissyslab.blocks.fanout import Broadcast
18
+ from dissyslab.blocks.fanin import MergeAsynch
19
+ from dissyslab.blocks.merge_synch import MergeSynch
20
+ from dissyslab.blocks.split import Split
21
+ from dissyslab.blocks.role import Role
22
+
23
+ __all__ = [
24
+ "Source",
25
+ "Transform",
26
+ "Sink",
27
+ "Broadcast",
28
+ "MergeAsynch",
29
+ "MergeSynch",
30
+ "Split",
31
+ "Role",
32
+ ]
@@ -0,0 +1,90 @@
1
+ # dissyslab/blocks/fanin.py
2
+ """
3
+ Merge Agents: Combine multiple inputs into single output (fanin).
4
+
5
+ MergeAsynch is the recommended merge for most use cases. Termination is
6
+ signaled by os_agent via _Shutdown, handled transparently by recv().
7
+ No STOP coordination needed.
8
+ """
9
+
10
+ from __future__ import annotations
11
+ from typing import Optional
12
+ import threading
13
+
14
+ from dissyslab.core import Agent, _ShutdownSignal
15
+
16
+
17
+ class MergeAsynch(Agent):
18
+ """
19
+ MergeAsynch agent: combines multiple inputs (fanin, non-deterministic).
20
+
21
+ Multiple inputs, single output. Receives from whichever input has
22
+ a message available first. Fast but non-deterministic order.
23
+
24
+ **Ports:**
25
+ - Inports: ["in_0", "in_1", ..., "in_{n-1}"]
26
+ - Outports: ["out_"]
27
+
28
+ **Termination:**
29
+ Termination is detected by os_agent and signaled via _Shutdown on
30
+ each inport, handled transparently by recv(). No STOP coordination
31
+ needed — worker threads exit cleanly via _ShutdownSignal.
32
+ """
33
+
34
+ def __init__(self, *, num_inputs: int, name: Optional[str] = None):
35
+ if num_inputs < 1:
36
+ raise ValueError(
37
+ f"MergeAsynch requires at least 1 input, got {num_inputs}"
38
+ )
39
+
40
+ inports = [f"in_{i}" for i in range(num_inputs)]
41
+ super().__init__(name=name, inports=inports, outports=["out_"])
42
+ self.num_inputs = num_inputs
43
+
44
+ @property
45
+ def default_inport(self) -> Optional[str]:
46
+ """No default input (multiple inputs - ambiguous)."""
47
+ return None
48
+
49
+ @property
50
+ def default_outport(self) -> str:
51
+ """Default output port for edge syntax."""
52
+ return "out_"
53
+
54
+ def _worker(self, port: str) -> None:
55
+ """
56
+ Worker thread for one input port.
57
+ Forwards messages to out_ until _ShutdownSignal is raised by recv().
58
+ """
59
+ try:
60
+ while True:
61
+ msg = self.recv(port)
62
+ self.send(msg, "out_")
63
+ except _ShutdownSignal:
64
+ pass # clean exit — os_agent declared termination
65
+
66
+ def run(self) -> None:
67
+ """
68
+ Spawn one worker thread per input port and wait for all to finish.
69
+
70
+ Workers exit when recv() raises _ShutdownSignal on _Shutdown receipt.
71
+ """
72
+ threads = []
73
+ for p in self.inports:
74
+ t = threading.Thread(
75
+ target=self._worker,
76
+ args=(p,),
77
+ name=f"merge_worker_{p}",
78
+ daemon=False
79
+ )
80
+ t.start()
81
+ threads.append(t)
82
+
83
+ for t in threads:
84
+ t.join()
85
+
86
+ def __repr__(self) -> str:
87
+ return f"<MergeAsynch name={self.name} inputs={self.num_inputs}>"
88
+
89
+ def __str__(self) -> str:
90
+ return f"MergeAsynch({self.num_inputs} inputs)"
@@ -0,0 +1,69 @@
1
+ # dissyslab/blocks/fanout.py
2
+ """
3
+ Broadcast Agent: Copies messages to multiple outputs (fanout).
4
+
5
+ Broadcast agents are automatically inserted by the framework when one
6
+ agent connects to multiple receivers. Termination is signaled by os_agent
7
+ via _Shutdown, handled transparently by recv().
8
+ """
9
+
10
+ from __future__ import annotations
11
+ from typing import Optional
12
+ import copy
13
+
14
+ from dissyslab.core import Agent
15
+
16
+
17
+ class Broadcast(Agent):
18
+ """
19
+ Broadcast agent: copies messages to multiple outputs (fanout).
20
+
21
+ Single input, multiple outputs. Receives message and sends a deep
22
+ copy to each output port.
23
+
24
+ **Ports:**
25
+ - Inports: ["in_"]
26
+ - Outports: ["out_0", "out_1", ..., "out_{n-1}"]
27
+
28
+ **Termination:**
29
+ Termination is detected by os_agent and signaled via _Shutdown,
30
+ which recv() handles transparently by raising _ShutdownSignal.
31
+ """
32
+
33
+ def __init__(self, *, num_outputs: int, name: Optional[str] = None):
34
+ if num_outputs < 1:
35
+ raise ValueError(
36
+ f"Broadcast requires at least 1 output, got {num_outputs}"
37
+ )
38
+
39
+ outports = [f"out_{i}" for i in range(num_outputs)]
40
+ super().__init__(name=name, inports=["in_"], outports=outports)
41
+ self.num_outputs = num_outputs
42
+
43
+ @property
44
+ def default_inport(self) -> str:
45
+ """Default input port for edge syntax."""
46
+ return "in_"
47
+
48
+ @property
49
+ def default_outport(self) -> Optional[str]:
50
+ """No default output (multiple outputs - ambiguous)."""
51
+ return None
52
+
53
+ def run(self) -> None:
54
+ """
55
+ Broadcast messages to all outputs.
56
+
57
+ recv() intercepts _Shutdown and raises _ShutdownSignal,
58
+ which unwinds this loop cleanly.
59
+ """
60
+ while True:
61
+ msg = self.recv("in_")
62
+ for i in range(self.num_outputs):
63
+ self.send(copy.deepcopy(msg), f"out_{i}")
64
+
65
+ def __repr__(self) -> str:
66
+ return f"<Broadcast name={self.name} outputs={self.num_outputs}>"
67
+
68
+ def __str__(self) -> str:
69
+ return f"Broadcast({self.num_outputs} outputs)"
@@ -0,0 +1,73 @@
1
+ # dissyslab/blocks/merge_synch.py
2
+ """
3
+ Merge Agents: Combine multiple inputs into single output.
4
+
5
+ MergeSynch: Synchronous merge in round-robin order (use sparingly).
6
+ For most use cases, prefer MergeAsynch in fanin.py.
7
+
8
+ Termination is signaled by os_agent via _Shutdown, handled transparently
9
+ by recv(). No explicit STOP handling needed.
10
+ """
11
+
12
+ from __future__ import annotations
13
+ from typing import Optional
14
+
15
+ from dissyslab.core import Agent
16
+
17
+
18
+ class MergeSynch(Agent):
19
+ """
20
+ MergeSynch agent: synchronously combines multiple inputs in round-robin order.
21
+
22
+ **WARNING: Use only when deterministic ordering is required AND all inputs
23
+ produce messages at similar rates. For most use cases, prefer MergeAsynch.**
24
+
25
+ **Ports:**
26
+ - Inports: ["in_0", "in_1", ..., "in_{n-1}"]
27
+ - Outports: ["out_"]
28
+
29
+ **Termination:**
30
+ Termination is detected by os_agent and signaled via _Shutdown,
31
+ which recv() handles transparently by raising _ShutdownSignal.
32
+ """
33
+
34
+ def __init__(self, *, num_inputs: int, name: Optional[str] = None):
35
+ if num_inputs < 1:
36
+ raise ValueError(
37
+ f"MergeSynch requires at least 1 input, got {num_inputs}"
38
+ )
39
+
40
+ inports = [f"in_{i}" for i in range(num_inputs)]
41
+ super().__init__(name=name, inports=inports, outports=["out_"])
42
+ self.num_inputs = num_inputs
43
+
44
+ @property
45
+ def default_inport(self) -> Optional[str]:
46
+ """No default input (multiple inputs - ambiguous)."""
47
+ return None
48
+
49
+ @property
50
+ def default_outport(self) -> str:
51
+ """Default output port for edge syntax."""
52
+ return "out_"
53
+
54
+ def run(self) -> None:
55
+ """
56
+ Synchronous merge loop: collect batches in round-robin order.
57
+
58
+ Collects one message from each input in order, sends as a list.
59
+ recv() intercepts _Shutdown and raises _ShutdownSignal,
60
+ which unwinds this loop cleanly.
61
+ """
62
+ while True:
63
+ batch = []
64
+ for inport in self.inports:
65
+ msg = self.recv(inport)
66
+ batch.append(msg)
67
+ self.send(batch, "out_")
68
+
69
+ def __repr__(self) -> str:
70
+ return f"<MergeSynch name={self.name} inputs={self.num_inputs}>"
71
+
72
+ def __str__(self) -> str:
73
+ return f"MergeSynch({self.num_inputs} inputs)"