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.
- dissyslab-1.0.2/LICENSE +15 -0
- dissyslab-1.0.2/PKG-INFO +188 -0
- dissyslab-1.0.2/README.md +142 -0
- dissyslab-1.0.2/dissyslab/__init__.py +25 -0
- dissyslab-1.0.2/dissyslab/blocks/__init__.py +32 -0
- dissyslab-1.0.2/dissyslab/blocks/fanin.py +90 -0
- dissyslab-1.0.2/dissyslab/blocks/fanout.py +69 -0
- dissyslab-1.0.2/dissyslab/blocks/merge_synch.py +73 -0
- dissyslab-1.0.2/dissyslab/blocks/role.py +113 -0
- dissyslab-1.0.2/dissyslab/blocks/sink.py +77 -0
- dissyslab-1.0.2/dissyslab/blocks/source.py +119 -0
- dissyslab-1.0.2/dissyslab/blocks/split.py +110 -0
- dissyslab-1.0.2/dissyslab/blocks/transform.py +82 -0
- dissyslab-1.0.2/dissyslab/builder.py +222 -0
- dissyslab-1.0.2/dissyslab/cli.py +239 -0
- dissyslab-1.0.2/dissyslab/components/__init__.py +24 -0
- dissyslab-1.0.2/dissyslab/components/sinks/__init__.py +13 -0
- dissyslab-1.0.2/dissyslab/components/sinks/console_display.py +66 -0
- dissyslab-1.0.2/dissyslab/components/sinks/demo_email_alerter.py +98 -0
- dissyslab-1.0.2/dissyslab/components/sinks/discard.py +28 -0
- dissyslab-1.0.2/dissyslab/components/sinks/file_system.py +70 -0
- dissyslab-1.0.2/dissyslab/components/sinks/gmail_sink.py +135 -0
- dissyslab-1.0.2/dissyslab/components/sinks/intelligence_display.py +123 -0
- dissyslab-1.0.2/dissyslab/components/sinks/llm_builders.py +36 -0
- dissyslab-1.0.2/dissyslab/components/sinks/mcp_sink.py +204 -0
- dissyslab-1.0.2/dissyslab/components/sinks/photo_dashboard.py +222 -0
- dissyslab-1.0.2/dissyslab/components/sinks/replay_csv_in.py +72 -0
- dissyslab-1.0.2/dissyslab/components/sinks/rl_dashboard.py +165 -0
- dissyslab-1.0.2/dissyslab/components/sinks/sine_mixture_source.py +160 -0
- dissyslab-1.0.2/dissyslab/components/sinks/sink_jsonl_recorder.py +58 -0
- dissyslab-1.0.2/dissyslab/components/sinks/sink_list_collector.py +33 -0
- dissyslab-1.0.2/dissyslab/components/sinks/sink_simple_file.py +38 -0
- dissyslab-1.0.2/dissyslab/components/sinks/test_llm_enricher.py +129 -0
- dissyslab-1.0.2/dissyslab/components/sinks/test_webhook.py +14 -0
- dissyslab-1.0.2/dissyslab/components/sinks/webhook_sink.py +186 -0
- dissyslab-1.0.2/dissyslab/components/sources/__init__.py +16 -0
- dissyslab-1.0.2/dissyslab/components/sources/bluesky_jetstream_source.py +235 -0
- dissyslab-1.0.2/dissyslab/components/sources/calendar_source.py +290 -0
- dissyslab-1.0.2/dissyslab/components/sources/cartpole_source.py +238 -0
- dissyslab-1.0.2/dissyslab/components/sources/clock_source.py +96 -0
- dissyslab-1.0.2/dissyslab/components/sources/demo_bluesky_jetstream.py +401 -0
- dissyslab-1.0.2/dissyslab/components/sources/demo_job_source.py +42 -0
- dissyslab-1.0.2/dissyslab/components/sources/demo_rss_source.py +292 -0
- dissyslab-1.0.2/dissyslab/components/sources/file_source.py +273 -0
- dissyslab-1.0.2/dissyslab/components/sources/generated/__init__.py +0 -0
- dissyslab-1.0.2/dissyslab/components/sources/generated/stocks_source.py +45 -0
- dissyslab-1.0.2/dissyslab/components/sources/generated/weather_source.py +111 -0
- dissyslab-1.0.2/dissyslab/components/sources/gmail_source.py +249 -0
- dissyslab-1.0.2/dissyslab/components/sources/image_folder_source.py +147 -0
- dissyslab-1.0.2/dissyslab/components/sources/list_source.py +30 -0
- dissyslab-1.0.2/dissyslab/components/sources/mcp_source.py +272 -0
- dissyslab-1.0.2/dissyslab/components/sources/natural_numbers_source.py +40 -0
- dissyslab-1.0.2/dissyslab/components/sources/rss_normalizer.py +328 -0
- dissyslab-1.0.2/dissyslab/components/sources/rss_source.py +196 -0
- dissyslab-1.0.2/dissyslab/components/sources/test_bluesky.py +16 -0
- dissyslab-1.0.2/dissyslab/components/sources/web_scraper.py +463 -0
- dissyslab-1.0.2/dissyslab/components/transformers/__init__.py +17 -0
- dissyslab-1.0.2/dissyslab/components/transformers/ai_agent.py +154 -0
- dissyslab-1.0.2/dissyslab/components/transformers/composition_analyzer.py +199 -0
- dissyslab-1.0.2/dissyslab/components/transformers/demo_ai_agent.py +230 -0
- dissyslab-1.0.2/dissyslab/components/transformers/demo_jobs.py +141 -0
- dissyslab-1.0.2/dissyslab/components/transformers/demo_salary.py +122 -0
- dissyslab-1.0.2/dissyslab/components/transformers/demo_sentiment.py +102 -0
- dissyslab-1.0.2/dissyslab/components/transformers/demo_spam.py +140 -0
- dissyslab-1.0.2/dissyslab/components/transformers/demo_topic.py +160 -0
- dissyslab-1.0.2/dissyslab/components/transformers/demo_urgency.py +149 -0
- dissyslab-1.0.2/dissyslab/components/transformers/exposure_analyzer.py +177 -0
- dissyslab-1.0.2/dissyslab/components/transformers/learning_curve_analyzer.py +187 -0
- dissyslab-1.0.2/dissyslab/components/transformers/policy_analyzer.py +168 -0
- dissyslab-1.0.2/dissyslab/components/transformers/prompts.py +563 -0
- dissyslab-1.0.2/dissyslab/components/transformers/reward_analyzer.py +141 -0
- dissyslab-1.0.2/dissyslab/components/transformers/sharpness_analyzer.py +149 -0
- dissyslab-1.0.2/dissyslab/components/transformers/stateful_agent.py +170 -0
- dissyslab-1.0.2/dissyslab/composed_agent.py +212 -0
- dissyslab-1.0.2/dissyslab/core.py +320 -0
- dissyslab-1.0.2/dissyslab/network.py +759 -0
- dissyslab-1.0.2/dissyslab/office/__init__.py +17 -0
- dissyslab-1.0.2/dissyslab/office/make_network.py +368 -0
- dissyslab-1.0.2/dissyslab/office/make_office.py +296 -0
- dissyslab-1.0.2/dissyslab/office/office_compiler.py +230 -0
- dissyslab-1.0.2/dissyslab/office/utils.py +916 -0
- dissyslab-1.0.2/dissyslab/os_agent.py +186 -0
- dissyslab-1.0.2/dissyslab/py.typed +0 -0
- dissyslab-1.0.2/dissyslab/utils/__init__.py +31 -0
- dissyslab-1.0.2/dissyslab/utils/get_credentials.py +172 -0
- dissyslab-1.0.2/dissyslab/utils/visualize.py +491 -0
- dissyslab-1.0.2/dissyslab.egg-info/PKG-INFO +188 -0
- dissyslab-1.0.2/dissyslab.egg-info/SOURCES.txt +96 -0
- dissyslab-1.0.2/dissyslab.egg-info/dependency_links.txt +1 -0
- dissyslab-1.0.2/dissyslab.egg-info/entry_points.txt +2 -0
- dissyslab-1.0.2/dissyslab.egg-info/requires.txt +18 -0
- dissyslab-1.0.2/dissyslab.egg-info/top_level.txt +1 -0
- dissyslab-1.0.2/pyproject.toml +109 -0
- dissyslab-1.0.2/setup.cfg +4 -0
- dissyslab-1.0.2/tests/test_compositionality.py +100 -0
- dissyslab-1.0.2/tests/test_compositionality_double.py +154 -0
- dissyslab-1.0.2/tests/test_mcp_source.py +37 -0
- dissyslab-1.0.2/tests/test_termination.py +101 -0
dissyslab-1.0.2/LICENSE
ADDED
|
@@ -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...
|
dissyslab-1.0.2/PKG-INFO
ADDED
|
@@ -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
|
+

|
|
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
|
+

|
|
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)"
|