nudemos 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nudemos-0.1.0/.gitignore +103 -0
- nudemos-0.1.0/PKG-INFO +49 -0
- nudemos-0.1.0/README.md +31 -0
- nudemos-0.1.0/pyproject.toml +38 -0
- nudemos-0.1.0/src/nudemos/__init__.py +43 -0
- nudemos-0.1.0/src/nudemos/agents/__init__.py +44 -0
- nudemos-0.1.0/src/nudemos/agents/board.py +189 -0
- nudemos-0.1.0/src/nudemos/agents/claude.py +31 -0
- nudemos-0.1.0/src/nudemos/agents/flows.py +454 -0
- nudemos-0.1.0/src/nudemos/agents/job.py +186 -0
- nudemos-0.1.0/src/nudemos/agents/launch.py +92 -0
- nudemos-0.1.0/src/nudemos/agents/orchestrate.py +260 -0
- nudemos-0.1.0/src/nudemos/agents/page.py +208 -0
- nudemos-0.1.0/src/nudemos/agents/state.py +151 -0
- nudemos-0.1.0/src/nudemos/agents/views.py +436 -0
- nudemos-0.1.0/src/nudemos/counter.py +84 -0
- nudemos-0.1.0/src/nudemos/memes/__init__.py +42 -0
- nudemos-0.1.0/src/nudemos/memes/book.py +959 -0
- nudemos-0.1.0/src/nudemos/memes/cells.py +272 -0
- nudemos-0.1.0/src/nudemos/memes/chain.py +302 -0
- nudemos-0.1.0/src/nudemos/memes/control.py +99 -0
- nudemos-0.1.0/src/nudemos/memes/curve.py +81 -0
- nudemos-0.1.0/src/nudemos/memes/job.py +319 -0
- nudemos-0.1.0/src/nudemos/memes/monitor.py +171 -0
- nudemos-0.1.0/src/nudemos/memes/overview.py +153 -0
- nudemos-0.1.0/src/nudemos/memes/state.py +128 -0
- nudemos-0.1.0/src/nudemos/memes/sync.py +161 -0
- nudemos-0.1.0/src/nudemos/memes/token.py +208 -0
- nudemos-0.1.0/src/nudemos/memes/views.py +607 -0
- nudemos-0.1.0/src/nudemos/mnist/__init__.py +87 -0
- nudemos-0.1.0/src/nudemos/mnist/compare.py +136 -0
- nudemos-0.1.0/src/nudemos/mnist/ink.py +267 -0
- nudemos-0.1.0/src/nudemos/mnist/interactions.py +193 -0
- nudemos-0.1.0/src/nudemos/mnist/job.py +198 -0
- nudemos-0.1.0/src/nudemos/mnist/launch.py +167 -0
- nudemos-0.1.0/src/nudemos/mnist/read.py +245 -0
- nudemos-0.1.0/src/nudemos/mnist/reader.py +149 -0
- nudemos-0.1.0/src/nudemos/mnist/run.py +287 -0
- nudemos-0.1.0/src/nudemos/mnist/state.py +141 -0
- nudemos-0.1.0/src/nudemos/mnist/trainer.py +406 -0
- nudemos-0.1.0/src/nudemos/mnist/views.py +476 -0
- nudemos-0.1.0/src/nudemos/movies/__init__.py +46 -0
- nudemos-0.1.0/src/nudemos/movies/cells.py +681 -0
- nudemos-0.1.0/src/nudemos/movies/data.py +254 -0
- nudemos-0.1.0/src/nudemos/movies/kit.py +500 -0
- nudemos-0.1.0/src/nudemos/sampled.py +136 -0
- nudemos-0.1.0/src/nudemos/tour.py +46 -0
nudemos-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
# The nuspace-ui package links its build/ to the vite output so an editable
|
|
13
|
+
# install serves it. A symlink, not a directory, but say so explicitly.
|
|
14
|
+
!pkgs/ts/nuspace-ui/python/nuspace_ui/build
|
|
15
|
+
develop-eggs/
|
|
16
|
+
dist/
|
|
17
|
+
downloads/
|
|
18
|
+
eggs/
|
|
19
|
+
.eggs/
|
|
20
|
+
lib64/
|
|
21
|
+
parts/
|
|
22
|
+
sdist/
|
|
23
|
+
var/
|
|
24
|
+
wheels/
|
|
25
|
+
share/python-wheels/
|
|
26
|
+
*.egg-info/
|
|
27
|
+
.installed.cfg
|
|
28
|
+
*.egg
|
|
29
|
+
MANIFEST
|
|
30
|
+
|
|
31
|
+
# Installer logs
|
|
32
|
+
pip-log.txt
|
|
33
|
+
pip-delete-this-directory.txt
|
|
34
|
+
|
|
35
|
+
# Unit test / coverage reports
|
|
36
|
+
htmlcov/
|
|
37
|
+
.tox/
|
|
38
|
+
.nox/
|
|
39
|
+
.coverage
|
|
40
|
+
.coverage.*
|
|
41
|
+
.cache
|
|
42
|
+
nosetests.xml
|
|
43
|
+
coverage.xml
|
|
44
|
+
*.cover
|
|
45
|
+
*.py,cover
|
|
46
|
+
.hypothesis/
|
|
47
|
+
.pytest_cache/
|
|
48
|
+
cover/
|
|
49
|
+
|
|
50
|
+
# Environments
|
|
51
|
+
.env
|
|
52
|
+
.venv
|
|
53
|
+
env/
|
|
54
|
+
venv/
|
|
55
|
+
ENV/
|
|
56
|
+
env.bak/
|
|
57
|
+
venv.bak/
|
|
58
|
+
|
|
59
|
+
# uv
|
|
60
|
+
uv.lock
|
|
61
|
+
|
|
62
|
+
# mypy
|
|
63
|
+
.mypy_cache/
|
|
64
|
+
.dmypy.json
|
|
65
|
+
dmypy.json
|
|
66
|
+
|
|
67
|
+
# IDEs
|
|
68
|
+
.idea/
|
|
69
|
+
.vscode/
|
|
70
|
+
|
|
71
|
+
.DS_Store
|
|
72
|
+
|
|
73
|
+
# Profiling
|
|
74
|
+
*.prof
|
|
75
|
+
*.lprof
|
|
76
|
+
|
|
77
|
+
# Benchmarking
|
|
78
|
+
benchmark_results/
|
|
79
|
+
*.benchmark
|
|
80
|
+
*.benchmarks
|
|
81
|
+
|
|
82
|
+
# Ruff
|
|
83
|
+
.ruff_cache/
|
|
84
|
+
|
|
85
|
+
# Misc
|
|
86
|
+
/*playground
|
|
87
|
+
.db*/
|
|
88
|
+
.state*/
|
|
89
|
+
.logs*/
|
|
90
|
+
|
|
91
|
+
# syncthing
|
|
92
|
+
.stfolder
|
|
93
|
+
|
|
94
|
+
# nuspace-ui (vite / node)
|
|
95
|
+
node_modules/
|
|
96
|
+
!src/nuspace/web/ui/dist/
|
|
97
|
+
src/nuspace/web/ui/dist/*
|
|
98
|
+
!src/nuspace/web/ui/dist/.gitkeep
|
|
99
|
+
|
|
100
|
+
*.db/
|
|
101
|
+
*.nuspace/
|
|
102
|
+
# Local archive of the v0 prototype (reference only, never edited)
|
|
103
|
+
_archive/
|
nudemos-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: nudemos
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: The Nu demos as nuspace Planes: a counter, a sampled series, a film base and MNIST trained live.
|
|
5
|
+
Project-URL: Repository, https://github.com/nustackdev/nuverse
|
|
6
|
+
Author-email: Gor Arakelyan <gorarkln@gmail.com>
|
|
7
|
+
License-Expression: Apache-2.0
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Requires-Dist: nuspace
|
|
10
|
+
Provides-Extra: agents
|
|
11
|
+
Requires-Dist: nustd[cc]; extra == 'agents'
|
|
12
|
+
Provides-Extra: memes
|
|
13
|
+
Requires-Dist: nustd[http]; extra == 'memes'
|
|
14
|
+
Provides-Extra: mnist
|
|
15
|
+
Requires-Dist: torch>=2.2; extra == 'mnist'
|
|
16
|
+
Requires-Dist: torchvision>=0.17; extra == 'mnist'
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
# nudemos
|
|
20
|
+
|
|
21
|
+
The Nu demos as [nuspace](https://github.com/nustackdev/nuspace) Planes `+` creates, under Demos:
|
|
22
|
+
|
|
23
|
+
- **Counter**: a count ticking once a second, kept in its cell's state.
|
|
24
|
+
- **Sampled**: a series that grows forever, charted from a reservoir sample.
|
|
25
|
+
- **Movies**: a film base where every film and person is its own live page.
|
|
26
|
+
- **MNIST**: train a small CNN, steer it while it trains, and track every run. Needs the `mnist` extra.
|
|
27
|
+
- **Memes**: a live terminal over the meme market on Solana: launches, trades and every coin's page, synced from public mainnet. Needs the `memes` extra.
|
|
28
|
+
- **Agents**: Claude Code sessions run, watched, steered and orchestrated from a space. One agent is one Claude Code session in a page, driven by a short Nu loop; fan-out with a judge, chains and task boards are a few lines of Nu each. Needs the `agents` extra and a logged-in `claude` CLI, no API key.
|
|
29
|
+
- **Nu demos**: all three at once, nested under one plane.
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
pip install nudemos
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
nuspace finds it on install through the `nuspace.extensions` entry point. You don't import it yourself.
|
|
36
|
+
|
|
37
|
+
## MNIST
|
|
38
|
+
|
|
39
|
+
Launch picks the knobs (optimizer, learning rate, batch size, channels, epochs, dropout) and starts a run: a page, and a headless job under it that trains in its own process. A run page shows the curves, twelve test digits and what the model reads in each, and every layer's weights and gradients over training. Drag the learning rate while it trains, stop it, resume it. Compare lines the runs up. Read puts a run's model to work: pick a run and its latest or best checkpoint, draw a digit, and it reads it on every stroke, showing the 28x28 image the model sees. Pick a run that is still training, or just launch one (the page follows the newest run until you pick), and the same drawing is read again as each checkpoint lands.
|
|
40
|
+
|
|
41
|
+
Nu owns the loop, torch owns the step: the job cell is a Nu loop over a trainer resource, reading its learning rate from state each chunk. Close the space mid run and open it again: the run carries on from its last checkpoint.
|
|
42
|
+
|
|
43
|
+
Models live in the space, not in files: each run keeps its latest checkpoint (what it resumes from) and the weights at its best test score, in its own state.
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
pip install "nudemos[mnist]"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
MNIST downloads once, on the first run, into `~/.cache/nuverse-mnist` (`$NUVERSE_MNIST_DATA` to move it). Runs train on cuda, or mps on Apple silicon, else the cpu (`$NUVERSE_MNIST_DEVICE` to pick). Three epochs take about a minute on a laptop cpu.
|
nudemos-0.1.0/README.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# nudemos
|
|
2
|
+
|
|
3
|
+
The Nu demos as [nuspace](https://github.com/nustackdev/nuspace) Planes `+` creates, under Demos:
|
|
4
|
+
|
|
5
|
+
- **Counter**: a count ticking once a second, kept in its cell's state.
|
|
6
|
+
- **Sampled**: a series that grows forever, charted from a reservoir sample.
|
|
7
|
+
- **Movies**: a film base where every film and person is its own live page.
|
|
8
|
+
- **MNIST**: train a small CNN, steer it while it trains, and track every run. Needs the `mnist` extra.
|
|
9
|
+
- **Memes**: a live terminal over the meme market on Solana: launches, trades and every coin's page, synced from public mainnet. Needs the `memes` extra.
|
|
10
|
+
- **Agents**: Claude Code sessions run, watched, steered and orchestrated from a space. One agent is one Claude Code session in a page, driven by a short Nu loop; fan-out with a judge, chains and task boards are a few lines of Nu each. Needs the `agents` extra and a logged-in `claude` CLI, no API key.
|
|
11
|
+
- **Nu demos**: all three at once, nested under one plane.
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
pip install nudemos
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
nuspace finds it on install through the `nuspace.extensions` entry point. You don't import it yourself.
|
|
18
|
+
|
|
19
|
+
## MNIST
|
|
20
|
+
|
|
21
|
+
Launch picks the knobs (optimizer, learning rate, batch size, channels, epochs, dropout) and starts a run: a page, and a headless job under it that trains in its own process. A run page shows the curves, twelve test digits and what the model reads in each, and every layer's weights and gradients over training. Drag the learning rate while it trains, stop it, resume it. Compare lines the runs up. Read puts a run's model to work: pick a run and its latest or best checkpoint, draw a digit, and it reads it on every stroke, showing the 28x28 image the model sees. Pick a run that is still training, or just launch one (the page follows the newest run until you pick), and the same drawing is read again as each checkpoint lands.
|
|
22
|
+
|
|
23
|
+
Nu owns the loop, torch owns the step: the job cell is a Nu loop over a trainer resource, reading its learning rate from state each chunk. Close the space mid run and open it again: the run carries on from its last checkpoint.
|
|
24
|
+
|
|
25
|
+
Models live in the space, not in files: each run keeps its latest checkpoint (what it resumes from) and the weights at its best test score, in its own state.
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
pip install "nudemos[mnist]"
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
MNIST downloads once, on the first run, into `~/.cache/nuverse-mnist` (`$NUVERSE_MNIST_DATA` to move it). Runs train on cuda, or mps on Apple silicon, else the cpu (`$NUVERSE_MNIST_DEVICE` to pick). Three epochs take about a minute on a laptop cpu.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "nudemos"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "The Nu demos as nuspace Planes: a counter, a sampled series, a film base and MNIST trained live."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "Apache-2.0"
|
|
11
|
+
authors = [{ name = "Gor Arakelyan", email = "gorarkln@gmail.com" }]
|
|
12
|
+
requires-python = ">=3.10"
|
|
13
|
+
dependencies = [
|
|
14
|
+
"nuspace",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[project.optional-dependencies]
|
|
18
|
+
mnist = [
|
|
19
|
+
"torch>=2.2",
|
|
20
|
+
"torchvision>=0.17",
|
|
21
|
+
]
|
|
22
|
+
# The memes demo's JSON-RPC calls go through nustd.http.
|
|
23
|
+
memes = [
|
|
24
|
+
"nustd[http]",
|
|
25
|
+
]
|
|
26
|
+
# The agents demo drives Claude Code through nustd.cc, on the local claude login.
|
|
27
|
+
agents = [
|
|
28
|
+
"nustd[cc]",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.urls]
|
|
32
|
+
Repository = "https://github.com/nustackdev/nuverse"
|
|
33
|
+
|
|
34
|
+
[project.entry-points."nuspace.extensions"]
|
|
35
|
+
nudemos = "nudemos:extension"
|
|
36
|
+
|
|
37
|
+
[tool.hatch.build.targets.wheel]
|
|
38
|
+
packages = ["src/nudemos"]
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""nudemos: the Nu demos as nuspace Planes.
|
|
2
|
+
|
|
3
|
+
- :mod:`.counter`: a count ticking once a second, in its cell's state.
|
|
4
|
+
- :mod:`.sampled`: a kh57 series growing forever, charted from a sample.
|
|
5
|
+
- :mod:`.tour`: the two above, nested under one plane.
|
|
6
|
+
- :mod:`.movies`: a film base, every film and person its own live page.
|
|
7
|
+
- :mod:`.mnist`: a CNN trained on MNIST, steered live, every run tracked. Needs the ``mnist`` extra.
|
|
8
|
+
- :mod:`.memes`: a live terminal over a meme market, synced from Solana.
|
|
9
|
+
- :mod:`.agents`: Claude Code sessions, run, steered and orchestrated from a space.
|
|
10
|
+
|
|
11
|
+
nuspace finds this package through the ``nuspace.extensions`` entry point,
|
|
12
|
+
which names :func:`extension`. Nothing is imported here at module scope.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from typing import TYPE_CHECKING
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from nuspace import Extension
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
__all__ = ["extension"]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def extension() -> Extension:
|
|
28
|
+
"""The tour and the demo planes, as one extension."""
|
|
29
|
+
from nuspace import Extension
|
|
30
|
+
|
|
31
|
+
from . import agents, counter, memes, mnist, movies, sampled, tour
|
|
32
|
+
|
|
33
|
+
return Extension(
|
|
34
|
+
planes=(
|
|
35
|
+
tour.PLANE,
|
|
36
|
+
counter.PLANE,
|
|
37
|
+
sampled.PLANE,
|
|
38
|
+
movies.PLANE,
|
|
39
|
+
mnist.PLANE,
|
|
40
|
+
memes.PLANE,
|
|
41
|
+
agents.PLANE,
|
|
42
|
+
)
|
|
43
|
+
)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""The ``agents`` Plane: Claude Code sessions run, watched, steered and orchestrated from a space.
|
|
2
|
+
|
|
3
|
+
Nu is the control plane, Claude Code does the work. One agent is one Claude
|
|
4
|
+
Code session: a page that streams its transcript, and a headless job under
|
|
5
|
+
the page whose one cell is a short Nu loop over a ``nustd.cc.Session``
|
|
6
|
+
(:mod:`.job`), taking each message from the agent's state and streaming the
|
|
7
|
+
turn back into it. The pages around it only read and steer that state.
|
|
8
|
+
|
|
9
|
+
Orchestration is plain Nu over agents, no framework: a flow is a headless job
|
|
10
|
+
whose cell asks agents, waits on them and reads their answers through a
|
|
11
|
+
:class:`~.flows.Team` (:mod:`.flows`). Three ship, each a plane under this one
|
|
12
|
+
showing its cell in full: Fan-out and judge, Chain, Task board.
|
|
13
|
+
|
|
14
|
+
- :mod:`.claude`, :mod:`.job`: the Claude Code endpoint, and the agent's loop over it.
|
|
15
|
+
- :mod:`.launch`: making an agent, saying something to it, stopping and resuming it.
|
|
16
|
+
- :mod:`.flows`: the :class:`~.flows.Team` terms and the three flows, as cell sources.
|
|
17
|
+
- :mod:`.board`, :mod:`.page`, :mod:`.orchestrate`: the planes, as cell sources.
|
|
18
|
+
- :mod:`.views`, :mod:`.state`: what the cells read and how they shape it.
|
|
19
|
+
|
|
20
|
+
Talking to Claude Code needs the ``claude-agent-sdk`` package (the ``cc`` extra
|
|
21
|
+
of nustd) and a logged-in ``claude`` CLI. No API key: it runs on the login.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
from nuspace import Plane
|
|
27
|
+
|
|
28
|
+
from . import board, orchestrate
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
__all__ = ["PLANE"]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
PLANE = Plane(
|
|
35
|
+
"agents",
|
|
36
|
+
"Agents",
|
|
37
|
+
icon="bot",
|
|
38
|
+
description="Claude Code sessions, run, steered and orchestrated from a space.",
|
|
39
|
+
meta={"editable": True, "full_width": True},
|
|
40
|
+
cells=(("intro", board.INTRO), ("new", board.NEW), ("board", board.BOARD)),
|
|
41
|
+
children=(orchestrate.FANOUT_PLANE, orchestrate.CHAIN_PLANE, orchestrate.TASKS_PLANE),
|
|
42
|
+
group="Demos",
|
|
43
|
+
backend="async",
|
|
44
|
+
)
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"""The Agents plane's own cells: what it is, a form that starts agents, and a card per agent.
|
|
2
|
+
|
|
3
|
+
- ``intro``: what the plane is, and where the flows are.
|
|
4
|
+
- ``new``: name, folder, model, permission mode and a brief, and two buttons:
|
|
5
|
+
Start agent makes one agent page with its job (:func:`~.launch.launch`) and
|
|
6
|
+
opens it; Run the demo starts the Fan-out flow on the built-in scenario,
|
|
7
|
+
three builders and a judge.
|
|
8
|
+
- ``board``: every agent as a card, live: its status, what it is doing, what it cost.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
__all__ = ["BOARD", "INTRO", "NEW"]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
INTRO = '''\
|
|
18
|
+
import nu
|
|
19
|
+
import nustd.ui
|
|
20
|
+
from nuspace import ops
|
|
21
|
+
from nuspace.system.utils import snap
|
|
22
|
+
|
|
23
|
+
TEXT = """\\
|
|
24
|
+
Claude Code sessions, run, watched, steered and orchestrated from a space, with Nu as the control plane.
|
|
25
|
+
|
|
26
|
+
One agent is one Claude Code session: a page that streams everything it says and does, and a headless job \\
|
|
27
|
+
under it that runs a short Nu loop over the session. Say something and it is the agent's next turn. \\
|
|
28
|
+
Stop it mid-turn, resume it later: it picks up the same conversation, even after the space restarts.
|
|
29
|
+
|
|
30
|
+
Orchestration is a few lines of Nu over agents, each flow a plane with its cell in full: \\
|
|
31
|
+
**Fan-out** (N agents on one brief, a judge picks the best), **Chain** (research, build, review) \\
|
|
32
|
+
and **Task board** (idle agents pull the next task).
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class Links(nustd.ui.Row):
|
|
37
|
+
fanout = nustd.ui.LinkRef.slot()
|
|
38
|
+
chain = nustd.ui.LinkRef.slot()
|
|
39
|
+
tasks = nustd.ui.LinkRef.slot()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class View(nustd.ui.Column):
|
|
43
|
+
text = nustd.ui.MarkdownRef.slot()
|
|
44
|
+
links = Links.slot(gap=4)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def links(kids):
|
|
48
|
+
def to(i):
|
|
49
|
+
return nu.Str("/") + nu.Str(nu.List(kids)[i])
|
|
50
|
+
|
|
51
|
+
L = View.links
|
|
52
|
+
return (
|
|
53
|
+
L.fanout.set(href=to(0), label="Fan-out and judge") >> L.chain.set(href=to(1), label="Chain")
|
|
54
|
+
>> L.tasks.set(href=to(2), label="Task board")
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def out(plane):
|
|
59
|
+
return View.text.set(TEXT) >> nu.let(snap(ops.children(plane)), links) >> nu.ForeverDo(nu.Delay(3600.0))
|
|
60
|
+
'''
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
NEW = """\
|
|
64
|
+
import nu
|
|
65
|
+
import nustd.ui
|
|
66
|
+
from nuspace import ops
|
|
67
|
+
from nuspace.system.utils import snap
|
|
68
|
+
from nudemos.agents import views
|
|
69
|
+
from nudemos.agents.claude import READY
|
|
70
|
+
from nudemos.agents.flows import SCENARIO, launch_flow
|
|
71
|
+
from nudemos.agents.launch import launch
|
|
72
|
+
from nudemos.agents.state import agents_of, flows_of, made_by
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def pick(options):
|
|
76
|
+
return type("Pick", (nustd.ui.Field,), {"choice": nustd.ui.SelectRef.slot(options=options)})
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class Who(nustd.ui.Grid):
|
|
80
|
+
name = nustd.ui.InputRef.slot(label="Name", placeholder="Name it, or leave it to us")
|
|
81
|
+
cwd = nustd.ui.InputRef.slot(label="Working folder", placeholder="A fresh temp folder")
|
|
82
|
+
model = pick(views.MODELS).slot(label="Model")
|
|
83
|
+
mode = pick(views.MODES).slot(label="Permission mode")
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class Go(nustd.ui.Row):
|
|
87
|
+
start = nustd.ui.ButtonRef.slot(label="Start agent", icon="play")
|
|
88
|
+
demo = nustd.ui.ButtonRef.slot(label="Run the demo: 3 builders and a judge", variant="secondary", icon="git-fork")
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class View(nustd.ui.Column):
|
|
92
|
+
nav = nustd.ui.NavRef.slot()
|
|
93
|
+
notice = nustd.ui.MarkdownRef.slot()
|
|
94
|
+
head = nustd.ui.HeadingRef.slot(level=4)
|
|
95
|
+
who = Who.slot(columns=4, min_width=180, gap=3)
|
|
96
|
+
brief = nustd.ui.TextAreaRef.slot(rows=3, auto_resize=True, placeholder="The brief: what should it do?")
|
|
97
|
+
go = Go.slot(gap=2, align="end")
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
W = View.who
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def started(root):
|
|
104
|
+
# One agent from what the form says, its page opened.
|
|
105
|
+
def made(spec):
|
|
106
|
+
def opened(page):
|
|
107
|
+
return launch(root, spec, page=page) >> views.go(View.nav, nu.Str("/") + nu.Str(page))
|
|
108
|
+
|
|
109
|
+
return nu.let("", opened) >> W.name.set("") >> View.brief.set("")
|
|
110
|
+
|
|
111
|
+
count = snap(agents_of(root).len())
|
|
112
|
+
spec = views.spec_of(
|
|
113
|
+
nu.Str(W.name), nu.Str(W.cwd), nu.Str(W.model.choice), nu.Str(W.mode.choice), nu.Str(View.brief), count
|
|
114
|
+
)
|
|
115
|
+
return nu.let(spec, made)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def demo(root):
|
|
119
|
+
# The built-in scenario: the Fan-out flow, three builders and a judge, on one brief.
|
|
120
|
+
fanout = nu.First(ops.children(root).iter().filter(lambda p: made_by(p) == "agents_fanout"))
|
|
121
|
+
|
|
122
|
+
def run(plane):
|
|
123
|
+
name = nu.Str("Fan-out ") + nu.str(nu.Int(snap(flows_of(plane).len())) + 1)
|
|
124
|
+
spec = nu.Dict.of(brief=SCENARIO, n=3, tasks=[])
|
|
125
|
+
return launch_flow(nu.Str(plane), root, "fanout", spec, name)
|
|
126
|
+
|
|
127
|
+
return nu.let(snap(nu.str(fanout).fallback("")), run)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def defaults():
|
|
131
|
+
return (
|
|
132
|
+
View.head.set("New agent") >> W.name.set("") >> W.cwd.set("")
|
|
133
|
+
>> W.model.choice.set(views.MODELS[0]) >> W.mode.choice.set(views.MODES[0])
|
|
134
|
+
>> View.brief.set("") >> View.go.start.set("Start agent", icon="play")
|
|
135
|
+
>> View.go.demo.set("Run the demo: 3 builders and a judge", variant="secondary", icon="git-fork")
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def out(plane):
|
|
140
|
+
if not READY:
|
|
141
|
+
return View.notice.set(views.MISSING) >> nu.ForeverDo(nu.Delay(3600.0))
|
|
142
|
+
root = nu.Str(plane)
|
|
143
|
+
return defaults() >> nu.ParallelAsync(
|
|
144
|
+
nu.ReactForever(View.go.start.on_click(), started(root)),
|
|
145
|
+
nu.ReactForever(View.go.demo.on_click(), demo(root)),
|
|
146
|
+
)
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
BOARD = """\
|
|
151
|
+
import nu
|
|
152
|
+
import nustd.ui
|
|
153
|
+
from nuspace.shapes import Space
|
|
154
|
+
from nuspace.system.utils import redraws
|
|
155
|
+
from nudemos.agents import views
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class AgentCard(nustd.ui.Card):
|
|
159
|
+
pill = nustd.ui.StatusPillRef.slot()
|
|
160
|
+
doing = nustd.ui.TextRef.slot()
|
|
161
|
+
stats = nustd.ui.TextRef.slot()
|
|
162
|
+
open = nustd.ui.LinkRef.slot(label="Open")
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class View(nustd.ui.Column):
|
|
166
|
+
head = nustd.ui.HeadingRef.slot(level=4)
|
|
167
|
+
empty = nustd.ui.EmptyStateRef.slot(label="No agents yet", description="Start one above, or run the demo.")
|
|
168
|
+
cards = nustd.ui.GridRef.slot(AgentCard, columns=3, min_width=280, gap=3)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def card(c):
|
|
172
|
+
k = View.cards[c["key"]]
|
|
173
|
+
return (
|
|
174
|
+
k.set_title(c["name"]) >> k.set_subtitle(c["where"]) >> k.pill.set(c["status"], tone=c["tone"])
|
|
175
|
+
>> k.doing.set(c["doing"]) >> k.stats.set(c["stats"]) >> k.open.set(href=c["href"], label="Open")
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def draw(cards):
|
|
180
|
+
each = nu.ForEachDo(nu.List(cards), card)
|
|
181
|
+
return nu.IfDo(nu.List(cards).len() > 0, View.empty.erase() >> each)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def out(plane):
|
|
185
|
+
kids = Space.planes[plane].children.on_change()
|
|
186
|
+
return View.head.set("All agents") >> View.empty.set("No agents yet") >> redraws(
|
|
187
|
+
[kids], views.cards(views.board(plane)), draw, every=1.0
|
|
188
|
+
)
|
|
189
|
+
"""
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""The Service an agent's job speaks to Claude Code through.
|
|
2
|
+
|
|
3
|
+
One streamed endpoint over ``nustd.cc``: a call is one turn, its events
|
|
4
|
+
handed over as they happen. Here, in a module, and not in the job's cell,
|
|
5
|
+
so the term that names it can travel to the job's worker process.
|
|
6
|
+
|
|
7
|
+
Importing this needs the ``claude-agent-sdk`` package; the planes check for
|
|
8
|
+
it before they start any agent (:data:`READY`).
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import importlib.util
|
|
14
|
+
|
|
15
|
+
import nu
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
__all__ = ["READY", "Claude"]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
#: Whether the Claude Agent SDK is here to talk to Claude Code with.
|
|
22
|
+
READY = importlib.util.find_spec("claude_agent_sdk") is not None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
if READY:
|
|
26
|
+
import nustd.cc
|
|
27
|
+
|
|
28
|
+
class Claude(nu.Service):
|
|
29
|
+
"""An agent's one Claude Code endpoint: a turn, streamed."""
|
|
30
|
+
|
|
31
|
+
say = nustd.cc.StreamRef.method()
|