zu-runtime 0.2.0__py3-none-any.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.
- zu/__init__.py +203 -0
- zu/pipeline.py +92 -0
- zu_runtime-0.2.0.dist-info/METADATA +115 -0
- zu_runtime-0.2.0.dist-info/RECORD +5 -0
- zu_runtime-0.2.0.dist-info/WHEEL +4 -0
zu/__init__.py
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"""Zu — the embed facade. ``import zu`` and run an agent in one line.
|
|
2
|
+
|
|
3
|
+
This is the batteries-included entry point for *using* Zu from your own code.
|
|
4
|
+
It wires the same path the CLI and the HTTP server use — config in, a typed
|
|
5
|
+
``Result`` out — so embedding, ``zu run``, and ``zu serve`` are one runtime, not
|
|
6
|
+
three.
|
|
7
|
+
|
|
8
|
+
import zu
|
|
9
|
+
|
|
10
|
+
# a self-contained agent: one agent.yaml, or a bundle dir (agent.yaml + tools/)
|
|
11
|
+
result = zu.run_agent("agent.yaml") # or zu.run_agent("my-agent/")
|
|
12
|
+
|
|
13
|
+
# the programmatic form — a config plus a task (config + many tasks):
|
|
14
|
+
result = zu.run(
|
|
15
|
+
{"query": "Extract the title and price.", "target": "https://example.com",
|
|
16
|
+
"output_schema": {"type": "object", "properties": {"title": {"type": "string"}}}},
|
|
17
|
+
config={"provider": {"name": "anthropic", "model": "claude-sonnet-4-6",
|
|
18
|
+
"api_key_env": "ANTHROPIC_API_KEY"},
|
|
19
|
+
"plugins": {"tools": ["http_fetch", "html_parse"], "validators": ["schema"]}},
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
print(result.status, result.value)
|
|
23
|
+
|
|
24
|
+
# a reusable, configured runner (load config once, run many tasks)
|
|
25
|
+
agent = zu.Zu(config="zu.yaml")
|
|
26
|
+
r1 = agent.run({"query": "..."})
|
|
27
|
+
r2, events = agent.run_with_events({"query": "..."}) # also get the event log
|
|
28
|
+
|
|
29
|
+
Credentials are never passed here: config names the *environment variable*
|
|
30
|
+
holding a key (``api_key_env``), resolved inside the adapter at call time.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
from __future__ import annotations
|
|
34
|
+
|
|
35
|
+
import asyncio
|
|
36
|
+
from typing import Any
|
|
37
|
+
|
|
38
|
+
from zu_cli.config import (
|
|
39
|
+
ConfigError,
|
|
40
|
+
RunConfig,
|
|
41
|
+
assemble,
|
|
42
|
+
coerce_config,
|
|
43
|
+
coerce_task,
|
|
44
|
+
)
|
|
45
|
+
from zu_core.contracts import Budget, Event, Result, Status, TaskSpec
|
|
46
|
+
from zu_core.loop import run_task
|
|
47
|
+
from zu_core.registry import (
|
|
48
|
+
backend,
|
|
49
|
+
detector,
|
|
50
|
+
provider,
|
|
51
|
+
sink,
|
|
52
|
+
tool,
|
|
53
|
+
validator,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
from .pipeline import Pipeline, PipelineResult
|
|
57
|
+
|
|
58
|
+
__version__ = "0.1.0"
|
|
59
|
+
|
|
60
|
+
# In-process plugin registration decorators, re-exported from the core registry
|
|
61
|
+
# so the documented ``@zu.tool`` / ``@zu.detector`` / … surface (see
|
|
62
|
+
# the architecture docs and AGENTS.md) actually resolves on ``import zu``. They
|
|
63
|
+
# register onto the process-wide REGISTRY the loop reads, so a decorator-
|
|
64
|
+
# registered plugin is visible to ``zu.run`` and ``zu plugins`` alike.
|
|
65
|
+
__all__ = [
|
|
66
|
+
"Zu",
|
|
67
|
+
"Pipeline",
|
|
68
|
+
"PipelineResult",
|
|
69
|
+
"run_agent",
|
|
70
|
+
"run_agent_with_events",
|
|
71
|
+
"run",
|
|
72
|
+
"arun",
|
|
73
|
+
"run_with_events",
|
|
74
|
+
"ConfigError",
|
|
75
|
+
"RunConfig",
|
|
76
|
+
"TaskSpec",
|
|
77
|
+
"Result",
|
|
78
|
+
"Status",
|
|
79
|
+
"Budget",
|
|
80
|
+
"Event",
|
|
81
|
+
"create_app",
|
|
82
|
+
"tool",
|
|
83
|
+
"detector",
|
|
84
|
+
"validator",
|
|
85
|
+
"provider",
|
|
86
|
+
"backend",
|
|
87
|
+
"sink",
|
|
88
|
+
"__version__",
|
|
89
|
+
]
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
# Config/task coercion is shared with the CLI surfaces (see zu_cli.config). The
|
|
93
|
+
# embed facade accepts a str task as a *path* (``allow_paths=True``): you're
|
|
94
|
+
# running in-process on your own host, so reading a task file you point at — the
|
|
95
|
+
# same affordance as ``zu run`` — is intended.
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class Zu:
|
|
99
|
+
"""A configured runner. Load a config once, run many tasks against it.
|
|
100
|
+
|
|
101
|
+
``config`` is a path, a dict, a ``RunConfig``, or None (``./zu.yaml``). The
|
|
102
|
+
config is parsed eagerly so a bad config fails here, not on the first run.
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
def __init__(self, config: Any = None) -> None:
|
|
106
|
+
self.config: RunConfig = coerce_config(config)
|
|
107
|
+
|
|
108
|
+
async def arun_with_events(self, task: Any) -> tuple[Result, list[Event]]:
|
|
109
|
+
"""Async: run one task, returning the Result and the run's event log."""
|
|
110
|
+
spec = coerce_task(task, self.config.budget, allow_paths=True)
|
|
111
|
+
provider, registry, bus, providers = assemble(self.config)
|
|
112
|
+
# The same observability hook the CLI uses: an embedded agent queues a
|
|
113
|
+
# blocked attempt to the review queue too (no console trace by default).
|
|
114
|
+
from zu_cli.observe import attach_observability
|
|
115
|
+
|
|
116
|
+
attach_observability(bus, self.config.observability)
|
|
117
|
+
try:
|
|
118
|
+
result = await run_task(
|
|
119
|
+
spec, provider, registry, bus,
|
|
120
|
+
providers=providers, containment=self.config.containment,
|
|
121
|
+
max_observation_chars=self.config.max_observation_chars,
|
|
122
|
+
observation_strategy=self.config.observation_strategy,
|
|
123
|
+
max_context_chars=self.config.max_context_chars,
|
|
124
|
+
)
|
|
125
|
+
events = await bus.query()
|
|
126
|
+
return result, events
|
|
127
|
+
finally:
|
|
128
|
+
# ``assemble`` builds a fresh bus (and its canonical/trace sinks) per
|
|
129
|
+
# run; release them here so a long-lived, reused ``Zu`` instance does
|
|
130
|
+
# not leak one sqlite connection per ``run()``.
|
|
131
|
+
await bus.aclose()
|
|
132
|
+
|
|
133
|
+
async def arun(self, task: Any) -> Result:
|
|
134
|
+
"""Async: run one task, returning just the Result."""
|
|
135
|
+
result, _ = await self.arun_with_events(task)
|
|
136
|
+
return result
|
|
137
|
+
|
|
138
|
+
def run_with_events(self, task: Any) -> tuple[Result, list[Event]]:
|
|
139
|
+
"""Run one task synchronously, returning the Result and the event log."""
|
|
140
|
+
return asyncio.run(self.arun_with_events(task))
|
|
141
|
+
|
|
142
|
+
def run(self, task: Any) -> Result:
|
|
143
|
+
"""Run one task synchronously, returning just the Result."""
|
|
144
|
+
return asyncio.run(self.arun(task))
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def run_agent(source: Any = None) -> Result:
|
|
148
|
+
"""Run a self-contained agent to a Result — the embed equivalent of
|
|
149
|
+
``zu run``. ``source`` is an ``agent.yaml`` path, a **bundle directory**
|
|
150
|
+
(agent.yaml + a tools/ package, auto-loaded), a dict, or None (``./agent.yaml``
|
|
151
|
+
or ``./``)."""
|
|
152
|
+
result, _ = run_agent_with_events(source)
|
|
153
|
+
return result
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def run_agent_with_events(source: Any = None) -> tuple[Result, list[Event]]:
|
|
157
|
+
"""Run a self-contained agent, returning the Result *and* its event log."""
|
|
158
|
+
return asyncio.run(_arun_agent(source))
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
async def _arun_agent(source: Any) -> tuple[Result, list[Event]]:
|
|
162
|
+
from zu_cli.config import load_agent
|
|
163
|
+
from zu_cli.observe import attach_observability
|
|
164
|
+
|
|
165
|
+
spec, cfg = load_agent(source)
|
|
166
|
+
provider, registry, bus, providers = assemble(cfg)
|
|
167
|
+
attach_observability(bus, cfg.observability)
|
|
168
|
+
try:
|
|
169
|
+
result = await run_task(
|
|
170
|
+
spec, provider, registry, bus,
|
|
171
|
+
providers=providers, containment=cfg.containment,
|
|
172
|
+
max_observation_chars=cfg.max_observation_chars,
|
|
173
|
+
observation_strategy=cfg.observation_strategy,
|
|
174
|
+
max_context_chars=cfg.max_context_chars,
|
|
175
|
+
)
|
|
176
|
+
return result, await bus.query()
|
|
177
|
+
finally:
|
|
178
|
+
await bus.aclose()
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def run(task: Any, config: Any = None) -> Result:
|
|
182
|
+
"""Run one task against a config — the programmatic form (config + many tasks).
|
|
183
|
+
``task`` and ``config`` may each be a path, a dict, the typed object, or None.
|
|
184
|
+
For a single self-contained ``agent.yaml``/bundle, use :func:`run_agent`."""
|
|
185
|
+
return Zu(config).run(task)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
async def arun(task: Any, config: Any = None) -> Result:
|
|
189
|
+
"""Async one-shot — the coroutine behind :func:`run`."""
|
|
190
|
+
return await Zu(config).arun(task)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def run_with_events(task: Any, config: Any = None) -> tuple[Result, list[Event]]:
|
|
194
|
+
"""Run one task to a Result *and* its event log (the queryable provenance)."""
|
|
195
|
+
return Zu(config).run_with_events(task)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def create_app(config: Any = None, **kwargs: Any) -> Any:
|
|
199
|
+
"""The ASGI app for ``zu serve``. Re-exported here so an embedder can mount
|
|
200
|
+
Zu in their own ASGI stack. Needs the 'serve' extra (FastAPI)."""
|
|
201
|
+
from zu_cli.server import create_app as _create_app
|
|
202
|
+
|
|
203
|
+
return _create_app(config, **kwargs)
|
zu/pipeline.py
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""``zu.Pipeline`` — the config-driven wrapper over the core orchestration.
|
|
2
|
+
|
|
3
|
+
The orchestration itself (shared-trace chaining, gating, resume) lives in
|
|
4
|
+
``zu_core.pipeline`` — pure, SDK-free core logic over ``run_task``. This wrapper
|
|
5
|
+
adds the ergonomics that match ``zu.run``: a YAML/dict **config** (one provider
|
|
6
|
+
block, the plugins, the sink) and **dict phase specs**. It assembles the config
|
|
7
|
+
once and shares the resulting bus across every phase.
|
|
8
|
+
|
|
9
|
+
pipe = zu.Pipeline(config="zu.yaml")
|
|
10
|
+
pipe.phase("extract", {"query": "...", "output_schema": {...}})
|
|
11
|
+
pipe.phase("summarize", lambda prev: {"query": f"...{prev.value['name']}...", "output_schema": {...}})
|
|
12
|
+
result = pipe.run() # PipelineResult: status, value, phases, events, id
|
|
13
|
+
|
|
14
|
+
A phase's task is a dict, or a callable ``(prev_result) -> dict`` that consumes
|
|
15
|
+
the previous phase's validated value. Point the config at the ``scripted``
|
|
16
|
+
provider to run the whole pipeline offline (no model, no network).
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import asyncio
|
|
22
|
+
from collections.abc import Callable
|
|
23
|
+
from typing import Any
|
|
24
|
+
from uuid import UUID, uuid4
|
|
25
|
+
|
|
26
|
+
from zu_cli.config import assemble, coerce_config, coerce_task
|
|
27
|
+
from zu_core.contracts import Result, TaskSpec
|
|
28
|
+
from zu_core.pipeline import Phase, PipelineResult, run_pipeline
|
|
29
|
+
|
|
30
|
+
__all__ = ["Pipeline", "PipelineResult"]
|
|
31
|
+
|
|
32
|
+
# A phase's task: a static spec dict, or a builder that consumes the prior result.
|
|
33
|
+
PhaseTask = dict | Callable[[Result | None], dict]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class Pipeline:
|
|
37
|
+
"""A deterministic sequence of phases sharing one trace, log, and budget gate.
|
|
38
|
+
|
|
39
|
+
``config`` is a path, dict, ``RunConfig``, or None (``./zu.yaml``) — the same
|
|
40
|
+
as ``zu.run``. ``pipeline_id`` defaults to a fresh id; pass a stable one (with
|
|
41
|
+
a durable ``event_sink`` in the config) to make the pipeline resumable across
|
|
42
|
+
process restarts.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
def __init__(self, config: Any = None, *, pipeline_id: UUID | str | None = None) -> None:
|
|
46
|
+
self.config = coerce_config(config)
|
|
47
|
+
self._id = _as_uuid(pipeline_id) if pipeline_id is not None else uuid4()
|
|
48
|
+
self._phases: list[tuple[str, PhaseTask]] = []
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def id(self) -> UUID:
|
|
52
|
+
return self._id
|
|
53
|
+
|
|
54
|
+
def phase(self, name: str, task: PhaseTask) -> Pipeline:
|
|
55
|
+
"""Append a phase. ``task`` is a spec dict or ``(prev_result) -> dict``.
|
|
56
|
+
Returns self, so calls chain. Names must be unique (they key resume)."""
|
|
57
|
+
if any(n == name for n, _ in self._phases):
|
|
58
|
+
raise ValueError(f"duplicate phase name: {name!r}")
|
|
59
|
+
self._phases.append((name, task))
|
|
60
|
+
return self
|
|
61
|
+
|
|
62
|
+
async def arun(self) -> PipelineResult:
|
|
63
|
+
cfg = self.config
|
|
64
|
+
provider, registry, bus, providers = assemble(cfg)
|
|
65
|
+
try:
|
|
66
|
+
phases = [Phase(name, self._build(task)) for name, task in self._phases]
|
|
67
|
+
return await run_pipeline(
|
|
68
|
+
phases, provider, registry, bus,
|
|
69
|
+
providers=providers, containment=cfg.containment, pipeline_id=self._id,
|
|
70
|
+
max_observation_chars=cfg.max_observation_chars,
|
|
71
|
+
observation_strategy=cfg.observation_strategy,
|
|
72
|
+
max_context_chars=cfg.max_context_chars,
|
|
73
|
+
)
|
|
74
|
+
finally:
|
|
75
|
+
# ``assemble`` built the bus + its sink(s); release them after the run.
|
|
76
|
+
await bus.aclose()
|
|
77
|
+
|
|
78
|
+
def run(self) -> PipelineResult:
|
|
79
|
+
"""Run the pipeline synchronously."""
|
|
80
|
+
return asyncio.run(self.arun())
|
|
81
|
+
|
|
82
|
+
def _build(self, task: PhaseTask) -> Callable[[Result | None], TaskSpec]:
|
|
83
|
+
"""Turn a dict / callable phase task into a core TaskSpec builder, coercing
|
|
84
|
+
the dict and inheriting the config's default budget."""
|
|
85
|
+
def build(prev: Result | None) -> TaskSpec:
|
|
86
|
+
task_dict = task(prev) if callable(task) else dict(task)
|
|
87
|
+
return coerce_task(task_dict, self.config.budget, allow_paths=True)
|
|
88
|
+
return build
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _as_uuid(value: UUID | str) -> UUID:
|
|
92
|
+
return value if isinstance(value, UUID) else UUID(str(value))
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: zu-runtime
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: An opinionated, backend-agnostic runtime for agents that work in production — deterministic, auditable, injection-resistant. Import as `zu`.
|
|
5
|
+
Project-URL: Homepage, https://github.com/k3-mt/zu
|
|
6
|
+
Project-URL: Repository, https://github.com/k3-mt/zu
|
|
7
|
+
License-Expression: Apache-2.0
|
|
8
|
+
Keywords: agents,ai-agents,event-sourcing,llm,runtime,sandbox,web-scraping
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
16
|
+
Classifier: Typing :: Typed
|
|
17
|
+
Requires-Python: >=3.11
|
|
18
|
+
Requires-Dist: zu-backends==0.2.0
|
|
19
|
+
Requires-Dist: zu-checks==0.2.0
|
|
20
|
+
Requires-Dist: zu-cli==0.2.0
|
|
21
|
+
Requires-Dist: zu-providers==0.2.0
|
|
22
|
+
Requires-Dist: zu-tools==0.2.0
|
|
23
|
+
Provides-Extra: all
|
|
24
|
+
Requires-Dist: zu-backends[docker]==0.2.0; extra == 'all'
|
|
25
|
+
Requires-Dist: zu-cli[mcp]==0.2.0; extra == 'all'
|
|
26
|
+
Requires-Dist: zu-cli[serve]==0.2.0; extra == 'all'
|
|
27
|
+
Requires-Dist: zu-cli[test]==0.2.0; extra == 'all'
|
|
28
|
+
Requires-Dist: zu-providers[anthropic]==0.2.0; extra == 'all'
|
|
29
|
+
Requires-Dist: zu-providers[openai]==0.2.0; extra == 'all'
|
|
30
|
+
Requires-Dist: zu-tools==0.2.0; extra == 'all'
|
|
31
|
+
Provides-Extra: anthropic
|
|
32
|
+
Requires-Dist: zu-providers[anthropic]==0.2.0; extra == 'anthropic'
|
|
33
|
+
Provides-Extra: demo
|
|
34
|
+
Requires-Dist: zu-tools==0.2.0; extra == 'demo'
|
|
35
|
+
Provides-Extra: docker
|
|
36
|
+
Requires-Dist: zu-backends[docker]==0.2.0; extra == 'docker'
|
|
37
|
+
Provides-Extra: mcp
|
|
38
|
+
Requires-Dist: zu-cli[mcp]==0.2.0; extra == 'mcp'
|
|
39
|
+
Provides-Extra: openai
|
|
40
|
+
Requires-Dist: zu-providers[openai]==0.2.0; extra == 'openai'
|
|
41
|
+
Provides-Extra: serve
|
|
42
|
+
Requires-Dist: zu-cli[serve]==0.2.0; extra == 'serve'
|
|
43
|
+
Provides-Extra: test
|
|
44
|
+
Requires-Dist: zu-cli[test]==0.2.0; extra == 'test'
|
|
45
|
+
Provides-Extra: web
|
|
46
|
+
Requires-Dist: zu-tools==0.2.0; extra == 'web'
|
|
47
|
+
Description-Content-Type: text/markdown
|
|
48
|
+
|
|
49
|
+
# Zu
|
|
50
|
+
|
|
51
|
+
**An opinionated, backend-agnostic runtime for agents that work in production** —
|
|
52
|
+
deterministic, auditable, and injection-resistant by construction.
|
|
53
|
+
|
|
54
|
+
New here? One line:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pip install 'zu-runtime[all]' # everything: web tools, both model SDKs, server, Docker, MCP
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Prefer a lean install? `pip install zu-runtime` gives you `import zu`, the `zu`
|
|
61
|
+
command, the **web tools** (http_fetch/html_parse/render_dom), the model-provider adapters,
|
|
62
|
+
detectors, validators, and a SQLite event sink. Add the heavy/situational bits as extras:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
pip install 'zu-runtime[anthropic]' # + the Anthropic SDK to call a real model (also: [openai])
|
|
66
|
+
pip install 'zu-runtime[serve]' # + the HTTP server (zu serve)
|
|
67
|
+
pip install 'zu-runtime[docker]' # + the Docker sandbox (tier-2 browser)
|
|
68
|
+
pip install 'zu-runtime[mcp]' # + the MCP server (zu mcp)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The `zu-*` packages are also standalone on PyPI, but you rarely install them individually —
|
|
72
|
+
that's for plugin authors depending on just `zu-core`.
|
|
73
|
+
|
|
74
|
+
## Embed it
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
import zu
|
|
78
|
+
|
|
79
|
+
result = zu.run(
|
|
80
|
+
{"query": "Extract the product name and price.",
|
|
81
|
+
"target": "https://example.com/product/123",
|
|
82
|
+
"output_schema": {"type": "object",
|
|
83
|
+
"properties": {"name": {"type": "string"}, "price": {"type": "string"}},
|
|
84
|
+
"required": ["name", "price"]}},
|
|
85
|
+
config={"provider": {"name": "anthropic", "model": "claude-sonnet-4-6",
|
|
86
|
+
"api_key_env": "ANTHROPIC_API_KEY"},
|
|
87
|
+
"plugins": {"tools": ["http_fetch", "html_parse", "render_dom"],
|
|
88
|
+
"detectors": ["empty", "error", "js-shell", "bot-wall"],
|
|
89
|
+
"validators": ["schema", "grounding"]}},
|
|
90
|
+
)
|
|
91
|
+
print(result.status, result.value)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Swapping the model is a one-line edit to the `provider` block — Anthropic,
|
|
95
|
+
OpenRouter, OpenAI, or a local model (Ollama / vLLM) — because the runtime only
|
|
96
|
+
ever speaks to a `ModelProvider` port. Credentials are named by environment
|
|
97
|
+
variable (`api_key_env`), never passed in code or config.
|
|
98
|
+
|
|
99
|
+
## Run it from the command line, or as a service
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
zu run agent.yaml # one-shot
|
|
103
|
+
zu run agent.yaml --every 5m # scheduled worker
|
|
104
|
+
zu serve -c agent.yaml # HTTP: POST /run (needs the [serve] extra)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## What it is
|
|
108
|
+
|
|
109
|
+
A small, stable core (the loop, registry, contracts, event bus) surrounded by
|
|
110
|
+
six swappable ports. Every capability that can vary is a plugin behind a port,
|
|
111
|
+
so the production system is reached by adding adapters — never by reopening the
|
|
112
|
+
core. Full source, architecture, and examples:
|
|
113
|
+
**https://github.com/k3-mt/zu**
|
|
114
|
+
|
|
115
|
+
Apache-2.0.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
zu/__init__.py,sha256=JpeAJGFgcfYQyjDh2GQBLTNVMq1HLUyWyiECbOacHF0,7491
|
|
2
|
+
zu/pipeline.py,sha256=1Ya7fDT5QFf_2NpNPyEVkLlcgHm4Zvy8TzrqjbHLt9g,4015
|
|
3
|
+
zu_runtime-0.2.0.dist-info/METADATA,sha256=QZBtUDvQ6V5uQ5J4QX9aQ0EymYWsFJql33AalJAswsQ,4718
|
|
4
|
+
zu_runtime-0.2.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
5
|
+
zu_runtime-0.2.0.dist-info/RECORD,,
|