graphcore-studio 0.2.0__py3-none-win_amd64.whl

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.
graphcore/__init__.py ADDED
@@ -0,0 +1,303 @@
1
+ """Dependency-free Python SDK for the GraphCore native runtime.
2
+
3
+ Build the shared library first and set GRAPHCORE_LIBRARY if it is outside build/.
4
+ State values are JSON encoded individually; Python objects stay out of checkpoints.
5
+ """
6
+ import asyncio
7
+ import ctypes as C
8
+ import inspect
9
+ import json
10
+ import os
11
+ from pathlib import Path
12
+ import sys
13
+ import threading
14
+ import traceback
15
+ from dataclasses import dataclass, field
16
+ from typing import Optional
17
+
18
+ END = "__end__"
19
+ __version__ = "0.2.0"
20
+
21
+ class GraphCoreError(RuntimeError):
22
+ pass
23
+
24
+ @dataclass
25
+ class Context:
26
+ resumed: bool = False
27
+ response: object = None
28
+
29
+ @dataclass
30
+ class Update:
31
+ values: dict = field(default_factory=dict)
32
+ next: Optional[str] = None
33
+
34
+ @dataclass
35
+ class Interrupt:
36
+ prompt: str
37
+
38
+ @dataclass
39
+ class Result:
40
+ state: dict
41
+ suspended: bool
42
+ prompt: str
43
+ steps: int
44
+
45
+ _EVENT = C.CFUNCTYPE(None, C.c_char_p, C.c_char_p, C.c_void_p)
46
+ _CALLBACK = C.CFUNCTYPE(C.c_int, C.c_char_p, C.c_int, C.c_char_p, C.c_void_p, C.c_void_p)
47
+
48
+ def _text(value):
49
+ if not isinstance(value, str) or "\0" in value:
50
+ raise ValueError("Expected a string without NUL characters")
51
+ return value.encode("utf-8")
52
+
53
+ def _dump(value):
54
+ return json.dumps(value, ensure_ascii=True, allow_nan=False, sort_keys=True)
55
+
56
+ def _encode(state):
57
+ return "".join(_text(k).hex() + ":" + _dump(v).encode().hex() + "\n"
58
+ for k, v in state.items()).encode()
59
+
60
+ def _decode(wire):
61
+ result = {}
62
+ for line in wire.decode().splitlines():
63
+ key, value = line.split(":", 1)
64
+ result[bytes.fromhex(key).decode()] = json.loads(bytes.fromhex(value).decode())
65
+ return result
66
+
67
+ def _load():
68
+ suffix = ".dll" if sys.platform == "win32" else ".dylib" if sys.platform == "darwin" else ".so"
69
+ name = ("" if sys.platform == "win32" else "lib") + "graphcore" + suffix
70
+ root = Path(__file__).resolve().parents[2]
71
+ candidates = [Path(os.environ["GRAPHCORE_LIBRARY"])] if "GRAPHCORE_LIBRARY" in os.environ else [
72
+ Path(__file__).parent / name, root / "build" / name, root / "build" / "Release" / name]
73
+ path = next((p for p in candidates if p.is_file()), None)
74
+ if path is None:
75
+ raise GraphCoreError("Native library missing. Build it first; see README or set GRAPHCORE_LIBRARY.")
76
+ lib = C.CDLL(str(path))
77
+ definitions = {
78
+ "gc_cancel": ([C.c_void_p], None),
79
+ "gc_set_observer": ([C.c_void_p, _EVENT, C.c_void_p], C.c_int),
80
+ "gc_add_condition": ([C.c_void_p, C.c_char_p, C.c_char_p, C.c_char_p, C.c_char_p, C.c_char_p], C.c_int),
81
+ "gc_create": ([C.c_char_p], C.c_void_p),
82
+ "gc_destroy": ([C.c_void_p], None),
83
+ "gc_error": ([C.c_void_p], C.c_char_p),
84
+ "gc_add_node": ([C.c_void_p, C.c_char_p, _CALLBACK, C.c_void_p, C.c_uint], C.c_int),
85
+ "gc_add_edge": ([C.c_void_p, C.c_char_p, C.c_char_p], C.c_int),
86
+ "gc_set_entry": ([C.c_void_p, C.c_char_p], C.c_int),
87
+ "gc_run": ([C.c_void_p, C.c_char_p, C.c_char_p, C.c_int, C.c_char_p, C.c_size_t], C.c_int),
88
+ "gc_inspect_checkpoint": ([C.c_void_p, C.c_char_p], C.c_int),
89
+ "gc_result": ([C.c_void_p], C.c_char_p),
90
+ "gc_prompt": ([C.c_void_p], C.c_char_p),
91
+ "gc_suspended": ([C.c_void_p], C.c_int),
92
+ "gc_steps": ([C.c_void_p], C.c_size_t),
93
+ "gc_reply_update": ([C.c_void_p, C.c_char_p, C.c_char_p], C.c_int),
94
+ "gc_reply_next": ([C.c_void_p, C.c_char_p], C.c_int),
95
+ "gc_reply_suspend": ([C.c_void_p, C.c_char_p], C.c_int),
96
+ "gc_reply_error": ([C.c_void_p, C.c_char_p], C.c_int),
97
+ }
98
+ for name, (args, result) in definitions.items():
99
+ fn = getattr(lib, name)
100
+ fn.argtypes, fn.restype = args, result
101
+ return lib
102
+
103
+ class Graph:
104
+ """Sequential durable graph. Use a separate Graph instance for concurrent runs.
105
+
106
+ Python callbacks return a dict, Update, or Interrupt. Async callbacks require
107
+ ainvoke/aresume. Explicitly close the graph or use it as a context manager.
108
+ """
109
+ def __init__(self, version="1"):
110
+ self._lib = _load()
111
+ self._handle = self._lib.gc_create(_text(version))
112
+ if not self._handle:
113
+ raise GraphCoreError("Cannot create graph")
114
+ self._callbacks = []
115
+ self._lock = threading.Lock()
116
+ self._loop = None
117
+ self._has_async = False
118
+ self._observer = None
119
+ self._lifecycle = threading.Lock()
120
+
121
+ def _check(self, code):
122
+ if code:
123
+ raise GraphCoreError(self._lib.gc_error(self._handle).decode())
124
+
125
+ def _enter(self):
126
+ if not self._lock.acquire(blocking=False):
127
+ raise GraphCoreError("Graph is in use; concurrent calls are unsupported")
128
+ if not self._handle:
129
+ self._lock.release()
130
+ raise GraphCoreError("Graph is closed")
131
+
132
+ def add_condition(self, name, field, expected, yes, no):
133
+ """Compare canonical JSON values and select a route entirely in C++."""
134
+ self._enter()
135
+ try:
136
+ self._check(self._lib.gc_add_condition(self._handle, _text(name), _text(field),
137
+ _text(_dump(expected)), _text(yes), _text(no)))
138
+ finally:
139
+ self._lock.release()
140
+ return self
141
+
142
+ def observe(self, callback):
143
+ def emit(kind, node, user):
144
+ try:
145
+ callback(kind.decode(), node.decode())
146
+ except Exception:
147
+ pass
148
+ self._enter()
149
+ try:
150
+ observer = _EVENT(emit)
151
+ self._check(self._lib.gc_set_observer(self._handle, observer, None))
152
+ self._observer = observer
153
+ finally:
154
+ self._lock.release()
155
+ return self
156
+
157
+ def cancel(self):
158
+ """Cooperatively cancel; use a new Graph after cancellation."""
159
+ with self._lifecycle:
160
+ if self._handle:
161
+ self._lib.gc_cancel(self._handle)
162
+
163
+ def add_node(self, name, function, retries=0):
164
+ if not callable(function):
165
+ raise TypeError("Node must be callable")
166
+ if not isinstance(retries, int) or not 0 <= retries <= 1000:
167
+ raise ValueError("retries must be between 0 and 1000")
168
+ is_async = inspect.iscoroutinefunction(function)
169
+ def call(wire, resumed, response, reply, user):
170
+ try:
171
+ context = Context(bool(resumed), json.loads(response) if resumed else None)
172
+ result = function(_decode(wire), context)
173
+ if inspect.isawaitable(result):
174
+ if self._loop is None:
175
+ if inspect.iscoroutine(result):
176
+ result.close()
177
+ raise GraphCoreError("Async callbacks require ainvoke/aresume")
178
+ async def await_result():
179
+ return await result
180
+ result = asyncio.run_coroutine_threadsafe(await_result(), self._loop).result()
181
+ if isinstance(result, Interrupt):
182
+ if self._lib.gc_reply_suspend(reply, _text(result.prompt)):
183
+ raise GraphCoreError("Cannot construct interrupt")
184
+ else:
185
+ update = result if isinstance(result, Update) else Update(result)
186
+ if not isinstance(update.values, dict):
187
+ raise TypeError("Node must return dict, Update, or Interrupt")
188
+ for key, value in update.values.items():
189
+ if self._lib.gc_reply_update(reply, _text(key), _text(_dump(value))):
190
+ raise GraphCoreError("Cannot construct update")
191
+ if update.next is not None and self._lib.gc_reply_next(reply, _text(update.next)):
192
+ raise GraphCoreError("Cannot construct route")
193
+ return 0
194
+ except BaseException:
195
+ self._lib.gc_reply_error(reply, traceback.format_exc().replace("\0", "\\0").encode())
196
+ return -1
197
+ callback = _CALLBACK(call)
198
+ self._enter()
199
+ try:
200
+ self._check(self._lib.gc_add_node(self._handle, _text(name), callback, None, retries))
201
+ self._callbacks.append(callback)
202
+ self._has_async |= is_async
203
+ finally:
204
+ self._lock.release()
205
+ return self
206
+
207
+ def add_edge(self, source, target):
208
+ self._enter()
209
+ try:
210
+ self._check(self._lib.gc_add_edge(self._handle, _text(source), _text(target)))
211
+ finally:
212
+ self._lock.release()
213
+ return self
214
+
215
+ def set_entry(self, name):
216
+ self._enter()
217
+ try:
218
+ self._check(self._lib.gc_set_entry(self._handle, _text(name)))
219
+ finally:
220
+ self._lock.release()
221
+ return self
222
+
223
+ def _run(self, state, checkpoint, resume, response, max_steps):
224
+ if not isinstance(max_steps, int) or max_steps < 1:
225
+ raise ValueError("max_steps must be positive")
226
+ path = _text(os.fspath(checkpoint)) if checkpoint is not None else None
227
+ reply = _text(_dump(response)) if response is not _MISSING else None
228
+ self._check(self._lib.gc_run(self._handle, _encode(state), path, resume, reply, max_steps))
229
+ return Result(_decode(self._lib.gc_result(self._handle)),
230
+ bool(self._lib.gc_suspended(self._handle)),
231
+ self._lib.gc_prompt(self._handle).decode(), self._lib.gc_steps(self._handle))
232
+
233
+ def inspect_checkpoint(self, checkpoint):
234
+ """Read saved state without executing callbacks or advancing the graph."""
235
+ self._enter()
236
+ try:
237
+ self._check(self._lib.gc_inspect_checkpoint(self._handle, _text(os.fspath(checkpoint))))
238
+ return Result(_decode(self._lib.gc_result(self._handle)),
239
+ bool(self._lib.gc_suspended(self._handle)),
240
+ self._lib.gc_prompt(self._handle).decode(), self._lib.gc_steps(self._handle))
241
+ finally:
242
+ self._lock.release()
243
+
244
+ def invoke(self, state=None, *, checkpoint=None, max_steps=100):
245
+ return self._sync(state or {}, checkpoint, False, _MISSING, max_steps)
246
+
247
+ def resume(self, checkpoint, *, response=None, max_steps=100):
248
+ return self._sync({}, checkpoint, True, response, max_steps)
249
+
250
+ def recover(self, checkpoint, *, max_steps=100):
251
+ return self._sync({}, checkpoint, True, _MISSING, max_steps)
252
+
253
+ def _sync(self, *args):
254
+ if self._has_async:
255
+ raise GraphCoreError("Async callbacks require ainvoke/aresume")
256
+ self._enter()
257
+ try:
258
+ return self._run(*args)
259
+ finally:
260
+ self._lock.release()
261
+
262
+ async def ainvoke(self, state=None, *, checkpoint=None, max_steps=100):
263
+ return await self._async(state or {}, checkpoint, False, _MISSING, max_steps)
264
+
265
+ async def aresume(self, checkpoint, *, response=None, max_steps=100):
266
+ return await self._async({}, checkpoint, True, response, max_steps)
267
+
268
+ async def arecover(self, checkpoint, *, max_steps=100):
269
+ return await self._async({}, checkpoint, True, _MISSING, max_steps)
270
+
271
+ async def _async(self, *args):
272
+ self._enter()
273
+ self._loop = asyncio.get_running_loop()
274
+ future = self._loop.run_in_executor(None, self._run, *args)
275
+ # Native execution cannot be safely abandoned. Keep references and loop
276
+ # association until it finishes, even if the awaiting caller cancels.
277
+ def finish(_):
278
+ self._loop = None
279
+ self._lock.release()
280
+ if not future.cancelled():
281
+ future.exception() # Retrieve errors if the caller was cancelled.
282
+ future.add_done_callback(finish)
283
+ return await asyncio.shield(future)
284
+
285
+ def close(self):
286
+ if not self._handle:
287
+ return
288
+ self._enter()
289
+ try:
290
+ with self._lifecycle:
291
+ self._lib.gc_destroy(self._handle)
292
+ self._handle = None
293
+ self._callbacks.clear()
294
+ finally:
295
+ self._lock.release()
296
+
297
+ def __enter__(self):
298
+ return self
299
+
300
+ def __exit__(self, *args):
301
+ self.close()
302
+
303
+ _MISSING = object()
Binary file
@@ -0,0 +1,171 @@
1
+ Metadata-Version: 2.4
2
+ Name: graphcore-studio
3
+ Version: 0.2.0
4
+ Summary: A local-first visual agent workflow studio powered by a native C++ graph runtime
5
+ Keywords: agents,agentic-workflows,graph,C++,visual-workflow-builder
6
+ License-Expression: MIT
7
+ License-File: LICENSE
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Environment :: Console
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Programming Language :: C++
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.9
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Requires-Python: >=3.9
21
+ Provides-Extra: models
22
+ Requires-Dist: openrouter<1,>=0.10; extra == "models"
23
+ Provides-Extra: pydantic
24
+ Requires-Dist: pydantic<3,>=2; extra == "pydantic"
25
+ Description-Content-Type: text/markdown
26
+
27
+ # GraphCore Studio
28
+
29
+ **Design local agent workflows on a canvas. Run them with a C++ engine. Extend them with Python.**
30
+
31
+ GraphCore Studio is an interactive, local-first visual agent framework. Drag nodes onto a canvas, connect agent handoffs, add conditional branches, validate structured output, call Python tools, and pause for human approval. Save and run workflows on your own machine.
32
+
33
+ ## Start Studio
34
+
35
+ Requirements: Python 3.9+ and a C++17 compiler. You do not need CMake, Node.js, Docker, API keys, an internet connection, or a model to try the included workflows.
36
+
37
+ From the project folder, run one command:
38
+
39
+ ```bash
40
+ python3 run_studio.py
41
+ ```
42
+
43
+ Open [http://127.0.0.1:8787](http://127.0.0.1:8787). The launcher compiles the C++ shared library on first run, then opens a server accessible only from your machine. On Windows, use PowerShell in a Visual Studio Developer Terminal with the C++ workload installed:
44
+
45
+ ```powershell
46
+ py run_studio.py
47
+ ```
48
+
49
+ If Python was installed through the Windows Store and `py` is unavailable, run `python run_studio.py` from the Visual Studio Developer Terminal.
50
+
51
+ Stop the server with Ctrl+C. Workflows and run history live in `studio/data/`; drafts are saved in this browser. Run `python3 run_studio.py --help` to configure the port, data directory, or Python tool plugins.
52
+
53
+ ### Install the PyPI package
54
+
55
+ After the first release is published, install the prebuilt package with:
56
+
57
+ ```bash
58
+ python -m pip install "graphcore-studio[models,pydantic]"
59
+ graphcore-studio
60
+ ```
61
+
62
+ The wheel bundles the C++ runtime and Studio UI. Source installs build the native runtime and therefore require a C++17 compiler and CMake. The distribution is named `graphcore-studio`; its Python API imports as `graphcore`.
63
+
64
+ ### Use Pydantic
65
+
66
+ The built-in structured-output validator works offline with no extra packages. To enable optional Pydantic v2 validation:
67
+
68
+ ```bash
69
+ python3 -m pip install -r requirements-pydantic.txt
70
+ python3 run_studio.py
71
+ ```
72
+
73
+ On Windows, replace `python3` with `py -3`. Restart Studio after installing packages. The Structured output node shows whether Pydantic is available.
74
+
75
+ ### Use OpenRouter models
76
+
77
+ The **Chat Model** and **Agent** nodes use GraphCore's model adapter with the official OpenRouter Python SDK. C++ owns graph scheduling, state commits, conditions, and checkpoints; the Python wrapper calls the SDK. The OpenRouter request runs through the project-owned adapter rather than a chain framework.
78
+
79
+ Install the SDK into the Python environment used to run Studio:
80
+
81
+ ```bash
82
+ python3 -m pip install -r requirements-models.txt
83
+ python3 run_studio.py
84
+ ```
85
+
86
+ For a PyPI installation, use the `models` extra instead: `python -m pip install "graphcore-studio[models]"`.
87
+
88
+ In Studio, open **Model integrations**, add `OPENROUTER_API_KEY`, and save. Select **OpenRouter** on a Chat Model node and enter a model ID from the [OpenRouter model catalog](https://openrouter.ai/models). Compose system/user/assistant messages and use **Insert variable** to reference workflow inputs and prior node outputs. Studio loads the local `.env` file automatically; secret values are not included in workflow files. The offline fixture needs no SDK, key, or network call.
89
+
90
+ ## Build a workflow
91
+
92
+ 1. Pick a template or drag a node from the left panel.
93
+ 2. Connect the output dots to the next node's input. Conditional nodes have separate true and false outputs.
94
+ 3. Select nodes to edit prompts, tools, schemas, and routing rules.
95
+ 4. Give the workflow JSON input in the panel under the canvas.
96
+ 5. Run it and inspect the output, state, and per-node trace.
97
+ 6. Save locally, export the workflow JSON, or approve a saved human checkpoint.
98
+
99
+ The starter templates cover research and review, structured output, Python tools, a supervisor delegating work to specialist agents, and a chat-model playground. The canvas also supports node search, keyboard undo/redo, zoom, pan, layout changes, validation, and run history.
100
+
101
+ Prompt templates reference state with `{{input}}`, `{{research}}`, or nested fields such as `{{record.summary}}`. Structured Output defines required fields and types. Python plugins extend the node's registered-tool list without placing executable source code in the workflow document.
102
+
103
+ ## Register a Python library or tool
104
+
105
+ A plugin is a local, trusted Python module. Studio loads it when the server starts. Import a library in your tool and register an ordinary function:
106
+
107
+ ```python
108
+ def register(add_tool):
109
+ from my_library import Client
110
+ client = Client()
111
+
112
+ def search(value, config):
113
+ return {"matches": client.search(str(value))}
114
+
115
+ add_tool("search", search, "Search our knowledge base")
116
+ ```
117
+
118
+ Start Studio with `python3 run_studio.py --plugin path/to/plugin.py`, then select the registered function in a Python Tool node. Plugins execute as the current user; load only modules you trust. The included `studio/example_plugin.py` shows a small plugin.
119
+
120
+ ## What runs where?
121
+
122
+ ```text
123
+ Visual editor & local HTTP API
124
+ │ workflow JSON and node registrations
125
+ Python integrations ───── OpenRouter SDK / Pydantic / your packages
126
+ │ Python C ABI
127
+ C++17 graph scheduler ─── conditions / routes / checkpoints
128
+ │
129
+ Local workflow & run files
130
+ ```
131
+
132
+ The C++ runtime owns graph validation at compile time, step scheduling, native equality conditions, and checkpoint transitions. Python supplies model and tool integrations and the local editor server. Model and Python tool code executes in Python; agent handoffs are sequential. Performance benefits depend on the workload. No blanket speedup claim is made.
133
+
134
+ ## Build or embed the C++ core
135
+
136
+ The editor launcher builds the native shared library automatically. To build the core tests and examples as well:
137
+
138
+ ```bash
139
+ ./scripts/build.sh
140
+ ```
141
+
142
+ For CMake users with CMake 3.16 or newer:
143
+
144
+ ```sh
145
+ cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
146
+ cmake --build build --config Release
147
+ ctest --test-dir build -C Release --output-on-failure
148
+ ```
149
+
150
+ Compile a C++ example directly with a compiler:
151
+
152
+ ```sh
153
+ c++ -std=c++17 -O2 -Iinclude src/graphcore.cpp examples/approval.cpp -o graphcore_demo
154
+ ```
155
+
156
+ The C++ interface is in `include/graphcore/graphcore.hpp`; the experimental C ABI used by Python is in `include/graphcore/c_api.h`.
157
+
158
+ ## Scope and guarantees
159
+
160
+ This is an early, working local framework. The UI has runnable templates and a native backend. The runtime currently executes one node at a time. Retries can repeat side effects; use idempotency keys in external systems. File checkpoints support local process-restart recovery but do not promise power-loss durability, multiple writers, encryption, or database transactions. Python async cancellation is cooperative only between callbacks. See the [execution contract](docs/SEMANTICS.md).
161
+
162
+ Parallel fan-out and joins, database-backed persistence, distributed worker coordination, and an authenticated server remain future work. A release workflow now builds CPython wheels for Linux, macOS, and Windows; those hosted builds still need to run before the wheels are verified for release. The local server uses a session token and origin checks, and binds only to loopback; it is not designed for public network exposure.
163
+
164
+ ## Project notes
165
+
166
+ - [Architecture and extension points](docs/ARCHITECTURE.md)
167
+ - [Local UI and runtime semantics](docs/SEMANTICS.md)
168
+ - [Roadmap](docs/ROADMAP.md)
169
+ - [Local verification record](docs/VALIDATION.md)
170
+ - [PyPI release checklist](docs/PYPI_RELEASE.md)
171
+ - [Example plugin](studio/example_plugin.py)
@@ -0,0 +1,28 @@
1
+ graphcore/__init__.py,sha256=7A3dUzs_gxgRho0k98uGT8OgdAAX8EYT5FU2UqdBneg,12408
2
+ graphcore/graphcore.dll,sha256=g07jMRtss_Wm6CkfnSKiAyJcxRF7Jab-2e8EI8YwJRs,111104
3
+ include/graphcore/c_api.h,sha256=H7IA88yP-Q9tazxr6Tx0QobFd-RqvuljzJQa9XNkIC0,2459
4
+ include/graphcore/graphcore.hpp,sha256=-pH7yfUTiGw6JmxJ-vkUgFLEnvE3jdKuDLWkLTCu3iA,3103
5
+ lib/cmake/GraphCore/GraphCoreConfig.cmake,sha256=jkFXP-6HfSoHZIpSGlRAZlNj-YOZodRj1KQ5HZ6lhRk,968
6
+ lib/cmake/GraphCore/GraphCoreConfigVersion.cmake,sha256=lj3aqD85__gyE1RZL3MnzO7Q4qpa-d-QG0LEXVbnKT8,2827
7
+ lib/cmake/GraphCore/GraphCoreTargets-release.cmake,sha256=31TYO8LSi6XmD7zN-dLOR9kK7Z934q5VGhhEOuGBmkA,896
8
+ lib/cmake/GraphCore/GraphCoreTargets.cmake,sha256=czj1gBdRN4ZU_mEuJbK2GL4jPgF7W9TkBgae5fAfnyE,4291
9
+ lib/graphcore.lib,sha256=sCtmI17LladE9z4aAV5tglsQkTlMXahW0Gf4czYLNQQ,1250444
10
+ lib/graphcore_c.lib,sha256=_HuQeiOH9u0DvB9VBpSRzqcMg7DZlo8LNjqgEwV8FTg,5100
11
+ studio/__init__.py,sha256=Cn0hUv9J3GXbu86e3qjzm5DFb2M8cOMRk_NX3oq2Ero,79
12
+ studio/cli.py,sha256=Xjdg0DNRMK_falfV63WZZzp5bPVZOsvJ0hPYZy8y5ro,268
13
+ studio/engine.py,sha256=_-XZaRlxjzfJRBXkzR2De3cttHLnB4Mvt1zyaqEVMfk,20118
14
+ studio/example_plugin.py,sha256=WtIwtZOkFapr99IKGet_5igN8VxdMVuxsO_d1NEsl6I,495
15
+ studio/server.py,sha256=lV7PbJJV3Lk-CIrcRlaVkFHHpVhLNpiUFlLzN9B5rbU,19834
16
+ studio/templates/01-research.json,sha256=IG3U8oCKj-rb96LEcCIPS77YRL1GFNja8IFofpRpoj0,2860
17
+ studio/templates/02-structured.json,sha256=jrX9r_rJoq_Exl9wBH_kyIfgfdJraHgPzJH8CIo2in4,2020
18
+ studio/templates/03-tools.json,sha256=Yh80vAmODCSZ9-JQX_8r_nmW8rSws1MG5U9ABcd5lbs,1494
19
+ studio/templates/04-team.json,sha256=mRoyXCA6ow3ZXFjhhkfQ3-b4K11lTgFkEsJsbbqGr9Y,2857
20
+ studio/templates/05-model.json,sha256=-BQq4fr2V62B1uVhZqrFX8CNbwAgJZNqlmcquaY95mk,1140
21
+ studio/web/app.js,sha256=2RlRUrSqW2o-xQ1TWbQ7QsiaEzO1Mtdq_u9dMpLMWkg,46513
22
+ studio/web/index.html,sha256=Ls0ePIekgN91D7fXbgEffQH1Dpn3t9qtyuLCeg1476w,5376
23
+ studio/web/style.css,sha256=RfxMCpe0LzNkR7AjSjO67P66mpo4Ln5gQlJvrBBfTmY,22282
24
+ graphcore_studio-0.2.0.dist-info/METADATA,sha256=66QnViLSScawTjd7JF3SBNGU-zQOD-mBqojN0lf13yk,8617
25
+ graphcore_studio-0.2.0.dist-info/WHEEL,sha256=QOQSSLDAidk39OMVpTfE4kjgcxpaKg4ohhjJQ7vAJpA,102
26
+ graphcore_studio-0.2.0.dist-info/entry_points.txt,sha256=ICHwg3dwPHf5dLOh88EOwvkWol7nGIHK6NWo6pmrnUU,54
27
+ graphcore_studio-0.2.0.dist-info/licenses/LICENSE,sha256=skdWxhD4isyrtl6JSxMWrkxVXlFV0Y5bIHulD9fHQUo,1100
28
+ graphcore_studio-0.2.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: scikit-build-core 1.1.0
3
+ Root-Is-Purelib: false
4
+ Tag: py3-none-win_amd64
5
+
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ graphcore-studio = studio.cli:main
3
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 GraphCore contributors
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, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,51 @@
1
+ #ifndef GRAPHCORE_C_API_H
2
+ #define GRAPHCORE_C_API_H
3
+ #include <stddef.h>
4
+ #if defined(_WIN32)
5
+ # if defined(GRAPHCORE_BUILD_SHARED)
6
+ # define GC_API __declspec(dllexport)
7
+ # else
8
+ # define GC_API __declspec(dllimport)
9
+ # endif
10
+ #else
11
+ # define GC_API __attribute__((visibility("default")))
12
+ #endif
13
+ #ifdef __cplusplus
14
+ extern "C" {
15
+ #endif
16
+ /* One handle per graph/run; handles must not be used concurrently.
17
+ Strings are UTF-8, NUL terminated. Returned strings live until the next call.
18
+ Callback state is hex wire format; callback and user data must outlive handle. */
19
+ typedef struct gc_handle gc_handle;
20
+ typedef int (*gc_callback)(const char* state, int resumed, const char* response,
21
+ void* reply, void* user_data);
22
+ typedef void (*gc_event_callback)(const char* type, const char* node, void* user_data);
23
+ /* Set observer before running; cancel is the only method safe during gc_run. */
24
+ GC_API int gc_set_observer(gc_handle*, gc_event_callback, void* user_data);
25
+ GC_API void gc_cancel(gc_handle*);
26
+ /* Native equality branch; values use the caller's state encoding. */
27
+ GC_API int gc_add_condition(gc_handle*, const char* name, const char* key,
28
+ const char* expected, const char* yes, const char* no);
29
+ GC_API gc_handle* gc_create(const char* version);
30
+ GC_API void gc_destroy(gc_handle*);
31
+ GC_API const char* gc_error(gc_handle*);
32
+ GC_API int gc_add_node(gc_handle*, const char* name, gc_callback, void* user_data, unsigned retries);
33
+ GC_API int gc_add_edge(gc_handle*, const char* source, const char* target);
34
+ GC_API int gc_set_entry(gc_handle*, const char* name);
35
+ /* checkpoint=NULL disables persistence; response=NULL means no resume response. */
36
+ GC_API int gc_run(gc_handle*, const char* initial_wire, const char* checkpoint,
37
+ int resume, const char* response, size_t max_steps);
38
+ GC_API int gc_inspect_checkpoint(gc_handle*, const char* path);
39
+ GC_API const char* gc_result(gc_handle*);
40
+ GC_API const char* gc_prompt(gc_handle*);
41
+ GC_API int gc_suspended(gc_handle*);
42
+ GC_API size_t gc_steps(gc_handle*);
43
+ /* Reply helpers return -1 on failure; callback must propagate failure. */
44
+ GC_API int gc_reply_update(void*, const char* key, const char* value);
45
+ GC_API int gc_reply_next(void*, const char* target);
46
+ GC_API int gc_reply_suspend(void*, const char* prompt);
47
+ GC_API int gc_reply_error(void*, const char* message);
48
+ #ifdef __cplusplus
49
+ }
50
+ #endif
51
+ #endif
@@ -0,0 +1,83 @@
1
+ #pragma once
2
+ #include <atomic>
3
+ #include <filesystem>
4
+ #include <functional>
5
+ #include <map>
6
+ #include <optional>
7
+ #include <stdexcept>
8
+ #include <string>
9
+ #include <vector>
10
+ #include <utility>
11
+
12
+ namespace graphcore {
13
+ using State = std::map<std::string, std::string>;
14
+ inline constexpr const char* END = "__end__";
15
+ struct Error : std::runtime_error { using std::runtime_error::runtime_error; };
16
+ struct NodeResult {
17
+ State updates;
18
+ std::optional<std::string> next;
19
+ bool suspend = false;
20
+ std::string prompt;
21
+ NodeResult(State changes = {}, std::optional<std::string> target = {},
22
+ bool paused = false, std::string question = {})
23
+ : updates(std::move(changes)), next(std::move(target)), suspend(paused),
24
+ prompt(std::move(question)) {}
25
+ };
26
+ struct Context { bool resumed = false; std::string response; };
27
+ using Node = std::function<NodeResult(const State&, const Context&)>;
28
+ struct NodeOptions { unsigned retries = 0; }; // Only opt in for safe/idempotent nodes.
29
+ struct Snapshot {
30
+ std::string graph_version, next;
31
+ State state;
32
+ std::size_t steps = 0;
33
+ bool suspended = false;
34
+ std::string prompt;
35
+ };
36
+ class CheckpointStore {
37
+ public:
38
+ virtual ~CheckpointStore() = default;
39
+ virtual void save(const Snapshot&) = 0;
40
+ virtual Snapshot load() = 0;
41
+ };
42
+ class FileCheckpoint final : public CheckpointStore {
43
+ std::filesystem::path path_;
44
+ public:
45
+ explicit FileCheckpoint(std::filesystem::path path) : path_(std::move(path)) {}
46
+ void save(const Snapshot&) override;
47
+ Snapshot load() override;
48
+ };
49
+ class Graph {
50
+ public:
51
+ struct Entry { Node fn; NodeOptions options; std::vector<std::string> targets; };
52
+ std::map<std::string, Entry> nodes;
53
+ std::string entry, version;
54
+ };
55
+ class GraphBuilder {
56
+ Graph graph_;
57
+ public:
58
+ explicit GraphBuilder(std::string version);
59
+ GraphBuilder& add_node(std::string name, Node node, NodeOptions options = {});
60
+ // All dynamic routes must be declared; the first is the default route.
61
+ GraphBuilder& edges(const std::string& source, std::vector<std::string> targets);
62
+ GraphBuilder& entry(std::string name);
63
+ Graph compile() const;
64
+ };
65
+ struct RunOptions {
66
+ std::size_t max_steps = 100;
67
+ const std::atomic<bool>* cancelled = nullptr;
68
+ // Observer exceptions are isolated from execution.
69
+ std::function<void(const std::string&, const std::string&)> on_event;
70
+ };
71
+ class Runtime {
72
+ Snapshot execute(const Graph&, Snapshot, CheckpointStore*, const RunOptions&, Context);
73
+ public:
74
+ Snapshot invoke(const Graph&, State initial = {}, CheckpointStore* store = nullptr,
75
+ const RunOptions& options = {});
76
+ // A response is required only when the saved snapshot is suspended.
77
+ Snapshot resume(const Graph&, CheckpointStore&, std::optional<std::string> response = {},
78
+ const RunOptions& options = {});
79
+ };
80
+ // Portable text wire format shared by the C ABI and Python wrapper.
81
+ std::string encode_state(const State&);
82
+ State decode_state(const std::string&);
83
+ }