microloop 0.3.0__cp310-abi3-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.
microloop/__init__.py ADDED
@@ -0,0 +1,236 @@
1
+ """
2
+ Microloop — Keep agents making progress.
3
+
4
+ A local reliability runtime for autonomous agents. It watches an agent's
5
+ execution trajectory, detects when the agent is looping, stalled, regressing,
6
+ or operating on stale state, and reports a configured intervention.
7
+
8
+ The public API is intentionally small: ``Monitor``, ``Policy``, ``Event``,
9
+ ``Decision`` and ``InterventionAction``. The host owns the agent loop; Microloop
10
+ only observes it and returns instructions.
11
+
12
+ ``SCHEMA_VERSION`` is the trajectory schema the ``microloop`` CLI reads. It is
13
+ not enforced here: the runtime ``Event`` has no version field and ignores unknown
14
+ keys, so ``Monitor.observe`` accepts any payload. Version checking happens at the
15
+ file boundary in :mod:`microloop.cli`.
16
+ """
17
+ from __future__ import annotations
18
+
19
+ import json
20
+ from collections.abc import Mapping
21
+ from dataclasses import dataclass, field
22
+ from typing import Any
23
+
24
+ from .microloop_core import Monitor as _CoreMonitor
25
+ from .microloop_core import Policy as _CorePolicy
26
+ from .microloop_core import version as _version
27
+
28
+ __version__: str = _version()
29
+ SCHEMA_VERSION = "0.3.0"
30
+
31
+ __all__ = [
32
+ "Decision",
33
+ "Event",
34
+ "InterventionAction",
35
+ "Monitor",
36
+ "Policy",
37
+ "ProgressState",
38
+ "SCHEMA_VERSION",
39
+ "__version__",
40
+ ]
41
+
42
+
43
+ class InterventionAction:
44
+ """What the host is advised to do. Compare with plain strings."""
45
+
46
+ Observe = "observe"
47
+ Replan = "replan"
48
+ Stop = "stop"
49
+
50
+
51
+ class ProgressState:
52
+ """Trajectory progress classification."""
53
+
54
+ Healthy = "healthy"
55
+ Warning = "warning"
56
+ Stalled = "stalled"
57
+ Regressing = "regressing"
58
+
59
+
60
+ @dataclass
61
+ class Event:
62
+ """One agent step."""
63
+
64
+ step: int
65
+ action: str
66
+ observation: str
67
+ state: Mapping[str, str] | None = None
68
+ metrics: Mapping[str, float] | None = None
69
+ metadata: Mapping[str, str] | None = None
70
+
71
+ def to_dict(self) -> dict[str, Any]:
72
+ payload: dict[str, Any] = {
73
+ "step": self.step,
74
+ "action": self.action,
75
+ "observation": self.observation,
76
+ }
77
+ if self.state is not None:
78
+ payload["state"] = dict(self.state)
79
+ if self.metrics is not None:
80
+ payload["metrics"] = dict(self.metrics)
81
+ if self.metadata is not None:
82
+ payload["metadata"] = dict(self.metadata)
83
+ return payload
84
+
85
+
86
+ @dataclass
87
+ class Decision:
88
+ """The runtime's classification of one step."""
89
+
90
+ step: int
91
+ status: str
92
+ reasons: list[str] = field(default_factory=list)
93
+ evidence: list[dict[str, Any]] = field(default_factory=list)
94
+ intervention: str = InterventionAction.Observe
95
+ severity: float = 0.0
96
+ verified_progress: bool = False
97
+ feedback: str | None = None
98
+
99
+ @classmethod
100
+ def from_dict(cls, payload: Mapping[str, Any]) -> Decision:
101
+ return cls(
102
+ step=int(payload["step"]),
103
+ status=str(payload["status"]),
104
+ reasons=[str(reason) for reason in payload.get("reasons", [])],
105
+ evidence=list(payload.get("evidence", [])),
106
+ intervention=str(payload.get("intervention", InterventionAction.Observe)),
107
+ severity=float(payload.get("severity", 0.0)),
108
+ verified_progress=bool(payload.get("verified_progress", False)),
109
+ feedback=payload.get("feedback"),
110
+ )
111
+
112
+ @property
113
+ def should_intervene(self) -> bool:
114
+ """True when the policy advises an action other than ``observe``."""
115
+ return self.intervention != InterventionAction.Observe
116
+
117
+ @property
118
+ def recovery_context(self) -> str:
119
+ """Recovery prompt to inject when intervening, or an empty string."""
120
+ return self.feedback or ""
121
+
122
+
123
+ class Policy:
124
+ """
125
+ Recovery policy: maps a progress state to an intervention.
126
+
127
+ Defaults to observation-only. Automatic ``replan`` or ``stop`` requires an
128
+ explicit opt-in.
129
+ """
130
+
131
+ def __init__(
132
+ self,
133
+ *,
134
+ healthy: str = InterventionAction.Observe,
135
+ warning: str = InterventionAction.Observe,
136
+ stalled: str = InterventionAction.Observe,
137
+ regressing: str = InterventionAction.Observe,
138
+ cooldown_steps: int = 5,
139
+ max_interventions: int = 2,
140
+ stop_at_step: int | None = None,
141
+ ) -> None:
142
+ config: dict[str, Any] = {
143
+ "healthy": healthy,
144
+ "warning": warning,
145
+ "stalled": stalled,
146
+ "regressing": regressing,
147
+ "cooldown_steps": cooldown_steps,
148
+ "max_interventions": max_interventions,
149
+ }
150
+ if stop_at_step is not None:
151
+ config["stop_at_step"] = stop_at_step
152
+ self._config = config
153
+ self._inner = _CorePolicy(json.dumps(config))
154
+
155
+ def evaluate(self, decision: Decision) -> str:
156
+ if not isinstance(decision, Decision):
157
+ raise TypeError("Policy.evaluate expects a Decision")
158
+ payload = {
159
+ "step": decision.step,
160
+ "status": decision.status,
161
+ "reasons": decision.reasons,
162
+ "evidence": decision.evidence,
163
+ "intervention": decision.intervention,
164
+ "severity": decision.severity,
165
+ "verified_progress": decision.verified_progress,
166
+ }
167
+ return json.loads(self._inner.evaluate_json(json.dumps(payload)))
168
+
169
+
170
+ class Monitor:
171
+ """
172
+ Trajectory progress monitor.
173
+
174
+ Defaults to observation-only. Automatic ``replan`` or ``stop`` requires an
175
+ explicit :class:`Policy`.
176
+ """
177
+
178
+ def __init__(
179
+ self,
180
+ *,
181
+ window: int = 32,
182
+ repetitions: int = 3,
183
+ stagnation_steps: int = 8,
184
+ verification_samples: int = 3,
185
+ normalization: bool = True,
186
+ policy: Policy | None = None,
187
+ ) -> None:
188
+ config = {
189
+ "window": window,
190
+ "repetitions": repetitions,
191
+ "stagnation_steps": stagnation_steps,
192
+ "verification_samples": verification_samples,
193
+ "normalization": normalization,
194
+ }
195
+ policy_json = json.dumps(policy._config) if policy is not None else None
196
+ self._inner = _CoreMonitor(json.dumps(config), policy_json)
197
+ self._step = 0
198
+
199
+ def observe(
200
+ self,
201
+ action: str,
202
+ observation: str,
203
+ *,
204
+ state: Mapping[str, str] | None = None,
205
+ metrics: Mapping[str, float] | None = None,
206
+ metadata: Mapping[str, str] | None = None,
207
+ step: int | None = None,
208
+ ) -> Decision:
209
+ """Observe one step and return its :class:`Decision`."""
210
+ if step is None:
211
+ self._step += 1
212
+ step = self._step
213
+ else:
214
+ self._step = max(self._step, step)
215
+ event = Event(
216
+ step=step,
217
+ action=action,
218
+ observation=observation,
219
+ state=state,
220
+ metrics=metrics,
221
+ metadata=metadata,
222
+ )
223
+ return Decision.from_dict(
224
+ json.loads(self._inner.observe_json(json.dumps(event.to_dict())))
225
+ )
226
+
227
+ def observe_event(self, event: Event) -> Decision:
228
+ """Observe a pre-built :class:`Event` (used by the CLI)."""
229
+ return self.observe(
230
+ action=event.action,
231
+ observation=event.observation,
232
+ state=event.state,
233
+ metrics=event.metrics,
234
+ metadata=event.metadata,
235
+ step=event.step,
236
+ )
microloop/cli.py ADDED
@@ -0,0 +1,317 @@
1
+ """Microloop CLI: inspect, replay, monitor and doctor.
2
+
3
+ Reads schema 0.3.0 trajectory JSONL and streams it through the native runtime.
4
+ ``monitor`` can follow a live file as an agent appends steps.
5
+
6
+ ``replay`` re-runs *recorded events* through the current Microloop engine. It
7
+ does not reproduce the original agent execution: no model is called and no tools
8
+ run. A trajectory recorded by an older engine may therefore classify differently
9
+ today.
10
+ """
11
+ from __future__ import annotations
12
+
13
+ import argparse
14
+ import json
15
+ import platform
16
+ import sys
17
+ import time
18
+ from typing import Any
19
+
20
+ from . import (
21
+ SCHEMA_VERSION,
22
+ Event,
23
+ InterventionAction,
24
+ Monitor,
25
+ Policy,
26
+ ProgressState,
27
+ __version__,
28
+ )
29
+
30
+ # Trajectory schema major version this runtime can read. A different major
31
+ # version changes the meaning of fields, so it is rejected rather than guessed.
32
+ _SCHEMA_MAJOR = int(SCHEMA_VERSION.split(".", 1)[0])
33
+
34
+ # Recommendation policy for `replay` and `monitor`: surfaces what a host *could*
35
+ # do without ever taking it. `inspect` deliberately does not use this; see below.
36
+ _RECOMMENDATION_POLICY = Policy(
37
+ warning=InterventionAction.Observe,
38
+ stalled=InterventionAction.Replan,
39
+ regressing=InterventionAction.Replan,
40
+ cooldown_steps=1,
41
+ max_interventions=10_000,
42
+ )
43
+
44
+ _COLORS = {
45
+ "healthy": "\033[32m",
46
+ "warning": "\033[33m",
47
+ "stalled": "\033[31m",
48
+ "regressing": "\033[35m",
49
+ }
50
+ _RESET = "\033[0m"
51
+
52
+
53
+ def _paint(text: str, status: str, enabled: bool) -> str:
54
+ if not enabled:
55
+ return text
56
+ return f"{_COLORS.get(status, '')}{text}{_RESET}"
57
+
58
+
59
+ def _check_schema(record: dict[str, Any], path: str, lineno: int) -> None:
60
+ """Reject a trajectory this runtime cannot read."""
61
+ raw = record.get("schema_version")
62
+ if raw is None:
63
+ return
64
+ version = str(raw)
65
+ major = version.partition(".")[0]
66
+ if not major.isdigit():
67
+ raise SystemExit(
68
+ f"{path}:{lineno}: unreadable schema_version {version!r}; "
69
+ f"expected '<major>.<minor>' (this runtime reads {SCHEMA_VERSION})"
70
+ )
71
+ if int(major) != _SCHEMA_MAJOR:
72
+ raise SystemExit(
73
+ f"{path}:{lineno}: incompatible trajectory schema {version}; "
74
+ f"this runtime reads schema {SCHEMA_VERSION}. Trajectory major versions "
75
+ "must match — upgrade Microloop, or re-record the trajectory with "
76
+ "this schema version."
77
+ )
78
+
79
+
80
+ def _load_record(path: str, lineno: int, line: str) -> dict[str, Any]:
81
+ try:
82
+ record = json.loads(line)
83
+ except json.JSONDecodeError as error:
84
+ raise SystemExit(f"{path}:{lineno}: invalid JSON: {error}") from error
85
+ if not isinstance(record, dict):
86
+ raise SystemExit(f"{path}:{lineno}: expected a JSON object")
87
+ _check_schema(record, path, lineno)
88
+ return record
89
+
90
+
91
+ def _iter_records(path: str, follow: bool = False, interval: float = 0.25):
92
+ """Yield trajectory records, optionally following a file as it grows."""
93
+ with open(path, encoding="utf-8") as handle:
94
+ lineno = 0
95
+ while True:
96
+ where = handle.tell()
97
+ raw = handle.readline()
98
+ if raw == "":
99
+ if not follow:
100
+ return
101
+ handle.seek(where)
102
+ time.sleep(interval)
103
+ continue
104
+ line = raw.strip()
105
+ if not line:
106
+ continue
107
+ lineno += 1
108
+ yield _load_record(path, lineno, line)
109
+
110
+
111
+ def _read_trajectory(path: str) -> tuple[str, list[dict[str, Any]]]:
112
+ schema = SCHEMA_VERSION
113
+ records: list[dict[str, Any]] = []
114
+ for record in _iter_records(path):
115
+ if "schema_version" in record:
116
+ schema = str(record["schema_version"])
117
+ records.append(record)
118
+ if not records:
119
+ raise SystemExit(f"{path}: no trajectory events found")
120
+ return schema, records
121
+
122
+
123
+ def _event_from_record(record: dict[str, Any], fallback_step: int) -> Event:
124
+ return Event(
125
+ step=int(record.get("step", fallback_step)),
126
+ action=str(record.get("action", "")),
127
+ observation=str(record.get("observation", "")),
128
+ state=record.get("state"),
129
+ metrics=record.get("metrics"),
130
+ metadata=record.get("metadata"),
131
+ )
132
+
133
+
134
+ def _recommending() -> Monitor:
135
+ return Monitor(policy=_RECOMMENDATION_POLICY)
136
+
137
+
138
+ def _observing() -> Monitor:
139
+ """Default runtime posture: detection without any intervention."""
140
+ return Monitor()
141
+
142
+
143
+ def commands() -> dict[str, Any]:
144
+ return {
145
+ "inspect": _inspect,
146
+ "replay": _replay,
147
+ "monitor": _monitor_live,
148
+ "doctor": _doctor,
149
+ }
150
+
151
+
152
+ def _inspect(args: argparse.Namespace) -> int:
153
+ schema, records = _read_trajectory(args.trajectory)
154
+ # Detection only: `inspect` reports what the detectors found, so it runs the
155
+ # default observation-only policy and never shows a host's replan/stop choice.
156
+ monitor = _observing()
157
+ first_detected: int | None = None
158
+ last = None
159
+ for index, record in enumerate(records, start=1):
160
+ decision = monitor.observe_event(_event_from_record(record, index))
161
+ if decision.status != ProgressState.Healthy and first_detected is None:
162
+ first_detected = decision.step
163
+ last = decision
164
+ assert last is not None
165
+
166
+ reasons = ", ".join(last.reasons) if last.reasons else "none"
167
+ evidence = last.evidence[0]["detail"] if last.evidence else "none"
168
+ print(f"Microloop trajectory analysis (schema {schema})")
169
+ print(f"{'Steps':<15}{len(records)}")
170
+ print(f"{'Status':<15}{last.status}")
171
+ print(f"{'Detected at':<15}{'step ' + str(first_detected) if first_detected else 'none'}")
172
+ print(f"{'Reasons':<15}{reasons}")
173
+ print(f"{'Evidence':<15}{evidence}")
174
+ print(f"{'Action':<15}{last.intervention} (default policy: observe only)")
175
+ return 0
176
+
177
+
178
+ def _replay(args: argparse.Namespace) -> int:
179
+ schema, records = _read_trajectory(args.trajectory)
180
+ monitor = _recommending()
181
+ for index, record in enumerate(records, start=1):
182
+ decision = monitor.observe_event(_event_from_record(record, index))
183
+ payload = {
184
+ "schema_version": schema,
185
+ "step": decision.step,
186
+ "status": decision.status,
187
+ "reasons": decision.reasons,
188
+ "intervention": decision.intervention,
189
+ "severity": decision.severity,
190
+ "verified_progress": decision.verified_progress,
191
+ }
192
+ if args.json:
193
+ print(json.dumps(payload))
194
+ else:
195
+ reasons = ",".join(decision.reasons) or "-"
196
+ print(
197
+ f"{decision.step:>4} {decision.status:<10} "
198
+ f"{decision.intervention:<8} {reasons}"
199
+ )
200
+ return 0
201
+
202
+
203
+ def _monitor_live(args: argparse.Namespace) -> int:
204
+ color = sys.stdout.isatty() and not args.no_color
205
+ monitor = _recommending()
206
+ steps = 0
207
+ stalls = 0
208
+ warnings = 0
209
+ regressions = 0
210
+ interventions = 0
211
+ recovered = False
212
+ seen_issue = False
213
+
214
+ print("Microloop")
215
+ print(f"trajectory {args.trajectory}" + (" (following)" if args.follow else ""))
216
+ print()
217
+ try:
218
+ for record in _iter_records(args.trajectory, follow=args.follow, interval=args.interval):
219
+ steps += 1
220
+ decision = monitor.observe_event(_event_from_record(record, steps))
221
+ if decision.status == "stalled":
222
+ stalls += 1
223
+ elif decision.status == "warning":
224
+ warnings += 1
225
+ elif decision.status == "regressing":
226
+ regressions += 1
227
+ if decision.status != "healthy":
228
+ seen_issue = True
229
+ elif seen_issue:
230
+ recovered = True
231
+
232
+ header = _paint(f"{decision.status.upper():<10}", decision.status, color)
233
+ reasons = " ".join(decision.reasons)
234
+ print(f"{decision.step:>4} {header} {reasons}".rstrip())
235
+ for item in decision.evidence:
236
+ print(f" {item['detail']}")
237
+ if decision.intervention != InterventionAction.Observe:
238
+ interventions += 1
239
+ print(f" -> {decision.intervention.upper()} (recommended)")
240
+ print()
241
+ except KeyboardInterrupt:
242
+ print()
243
+
244
+ print("completed" if not seen_issue or recovered else "stopped")
245
+ print(f"{'Steps':<18}{steps}")
246
+ print(f"{'Stalls':<18}{stalls}")
247
+ print(f"{'Warnings':<18}{warnings}")
248
+ print(f"{'Regressions':<18}{regressions}")
249
+ print(f"{'Recommended':<18}{interventions}")
250
+ print(f"{'Recovered':<18}{'yes' if recovered else 'no'}")
251
+ return 0
252
+
253
+
254
+ def _doctor(_args: argparse.Namespace) -> int:
255
+ checks = [
256
+ ("microloop", __version__),
257
+ ("schema", SCHEMA_VERSION),
258
+ ("runtime", "native (PyO3)"),
259
+ ("python", platform.python_version()),
260
+ ("platform", f"{platform.system()} {platform.machine()}"),
261
+ ]
262
+ ok = True
263
+ for name, value in checks:
264
+ print(f"[ok] {name:<10} {value}")
265
+ try:
266
+ from .microloop_core import Monitor as _Native # noqa: F401
267
+
268
+ print("[ok] native extension importable")
269
+ except Exception as error: # pragma: no cover - environment dependent
270
+ ok = False
271
+ print(f"[fail] native extension: {error}")
272
+ return 0 if ok else 1
273
+
274
+
275
+ def build_parser() -> argparse.ArgumentParser:
276
+ parser = argparse.ArgumentParser(prog="microloop", description="Microloop runtime tools")
277
+ parser.add_argument(
278
+ "--version",
279
+ action="version",
280
+ version=f"microloop {__version__} (schema {SCHEMA_VERSION}, runtime: native)",
281
+ )
282
+ sub = parser.add_subparsers(dest="command")
283
+ inspect_parser = sub.add_parser(
284
+ "inspect",
285
+ help="summarize a trajectory JSONL (detection only, no intervention policy)",
286
+ )
287
+ inspect_parser.add_argument("trajectory")
288
+ replay_parser = sub.add_parser(
289
+ "replay",
290
+ help="re-run recorded events through the current engine",
291
+ )
292
+ replay_parser.add_argument("trajectory")
293
+ replay_parser.add_argument("--json", action="store_true", help="emit decisions as JSONL")
294
+ monitor_parser = sub.add_parser("monitor", help="print a live progress view")
295
+ monitor_parser.add_argument("trajectory")
296
+ monitor_parser.add_argument(
297
+ "-f", "--follow", action="store_true", help="keep reading as the file grows"
298
+ )
299
+ monitor_parser.add_argument(
300
+ "--interval", type=float, default=0.25, help="poll interval in seconds when following"
301
+ )
302
+ monitor_parser.add_argument("--no-color", action="store_true", help="disable ANSI color")
303
+ sub.add_parser("doctor", help="check the native runtime")
304
+ return parser
305
+
306
+
307
+ def main(argv: list[str] | None = None) -> int:
308
+ parser = build_parser()
309
+ args = parser.parse_args(argv)
310
+ if not args.command:
311
+ parser.print_help()
312
+ return 0
313
+ return commands()[args.command](args)
314
+
315
+
316
+ if __name__ == "__main__": # pragma: no cover
317
+ sys.exit(main())
Binary file
microloop/py.typed ADDED
File without changes
@@ -0,0 +1,80 @@
1
+ Metadata-Version: 2.4
2
+ Name: microloop
3
+ Version: 0.3.0
4
+ Classifier: Programming Language :: Rust
5
+ Classifier: Programming Language :: Python :: 3
6
+ Classifier: Programming Language :: Python :: 3.10
7
+ Classifier: Programming Language :: Python :: 3.11
8
+ Classifier: Programming Language :: Python :: 3.12
9
+ Classifier: Programming Language :: Python :: 3.13
10
+ Classifier: Programming Language :: Python :: Implementation :: CPython
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: License :: OSI Approved :: Apache Software License
13
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
14
+ Classifier: Development Status :: 4 - Beta
15
+ License-File: LICENSE-MIT
16
+ License-File: LICENSE-APACHE
17
+ Summary: Local reliability runtime for autonomous agents
18
+ Keywords: agent,llm,trajectory,progress,loop-detection
19
+ Author: Microloop Team
20
+ License-Expression: MIT OR Apache-2.0
21
+ Requires-Python: >=3.10, <3.14
22
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
23
+ Project-URL: Homepage, https://github.com/Devaretanmay/microloop
24
+ Project-URL: Repository, https://github.com/Devaretanmay/microloop
25
+
26
+ # Microloop Python SDK
27
+
28
+ Python bindings for the Microloop local reliability runtime.
29
+
30
+ ```python
31
+ from microloop import InterventionAction, Monitor, Policy
32
+
33
+ policy = Policy(
34
+ stalled=InterventionAction.Replan,
35
+ regressing=InterventionAction.Stop,
36
+ cooldown_steps=5,
37
+ )
38
+ monitor = Monitor(policy=policy)
39
+
40
+ for step in agent.steps():
41
+ decision = monitor.observe(
42
+ action=step.action,
43
+ observation=step.result,
44
+ state=step.state,
45
+ metrics={"exit_code": step.exit_code},
46
+ )
47
+ if decision.should_intervene:
48
+ agent.inject(decision.recovery_context)
49
+ ```
50
+
51
+ `Monitor.observe(...)` returns a `Decision` with `status`, `reasons`,
52
+ `intervention`, `severity`, `verified_progress` and `feedback`. The default
53
+ policy only observes; automatic recovery requires explicit opt-in.
54
+
55
+ You own the agent loop. `Monitor` classifies and advises; it never runs tools,
56
+ calls a model, or stops a process.
57
+
58
+ ## CLI
59
+
60
+ ```bash
61
+ microloop inspect trajectory.jsonl # detection only, default observe-only policy
62
+ microloop replay trajectory.jsonl --json # re-run recorded events through the engine
63
+ microloop monitor trajectory.jsonl --follow
64
+ microloop doctor
65
+ ```
66
+
67
+ `replay` re-runs *recorded events* through the current engine. It does not
68
+ reproduce the original agent execution. A trajectory with an incompatible
69
+ `schema_version` major version is rejected rather than analyzed.
70
+
71
+ ## Building
72
+
73
+ ```bash
74
+ pip install maturin
75
+ maturin develop --manifest-path python/microloop/Cargo.toml
76
+ ```
77
+
78
+ No external credentials or network connections are required. All trajectory
79
+ evaluation runs locally in Rust via PyO3.
80
+
@@ -0,0 +1,11 @@
1
+ microloop/__init__.py,sha256=TyEdfEbnggSZH7fIP9AFWLkSjHZbEWv866OJb0fdwe0,7563
2
+ microloop/cli.py,sha256=AkjqrI7_msaYJLLoq-__XXXWKQp7x1MTQ9o6A1N5i7o,11285
3
+ microloop/microloop_core.pyd,sha256=6ELkzbzuXtfetELCBfR1qR8y0WfSz4m-fyQArJugkec,1214976
4
+ microloop/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ microloop-0.3.0.dist-info/METADATA,sha256=xyGPUmORrizxF1b0VLTJWfm2AZc9m3tCFGP3VmR1f5I,2775
6
+ microloop-0.3.0.dist-info/WHEEL,sha256=7HL-TGvq44Ofp0yhZL-uRIIFHcJTIqqkRZVQ2wZidzM,96
7
+ microloop-0.3.0.dist-info/entry_points.txt,sha256=IH9dbn7NkvU8eRSKXpvNhYBsPVuIqll-DqFEelqfLTs,47
8
+ microloop-0.3.0.dist-info/licenses/LICENSE-APACHE,sha256=rbE9VbG9z2yR4NDeFiwhzbli2eDjiXYeUIhQIvcUdM8,11548
9
+ microloop-0.3.0.dist-info/licenses/LICENSE-MIT,sha256=NRAxjDGOWZmFNvH50bZgkWzQLXQKLyJrPmrR_K5KJsA,1087
10
+ microloop-0.3.0.dist-info/sboms/microloop-python.cyclonedx.json,sha256=cAVkmwmBV3p1i8JkwBpFVHtK33fWMs1sKZ7YNkMQl_4,48880
11
+ microloop-0.3.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: maturin (1.15.0)
3
+ Root-Is-Purelib: false
4
+ Tag: cp310-abi3-win_amd64
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ microloop=microloop.cli:main