nodloop 0.7.0__tar.gz → 0.8.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: nodloop
3
- Version: 0.7.0
3
+ Version: 0.8.0
4
4
  Summary: Record AI runs and the verdicts people give on them, and hand the knowledge they approve to the next run in the same place
5
5
  Project-URL: Homepage, https://github.com/jeon-jihyeon/nodloop
6
6
  Project-URL: Repository, https://github.com/jeon-jihyeon/nodloop
@@ -54,7 +54,7 @@ From then on every `knowledge("support-bot", acme)` carries the item, and a run
54
54
  | `record(producer, output, *, labels, applied, subject)` | the run id a verdict cites |
55
55
  | `judge(run, verdict, reason, reason_code, edited, reviewer)` | nothing |
56
56
  | `propose(kind, content, from_run, producer, labels, new_labels, item_id, veto)` | `Candidate` with `id`, `version`, `status` and `overlaps`, the current items of the same kind one run may carry with it |
57
- | `approve(item_id, version, approver)` | `Approval` with `id`, `version`, `status`, `approver`, `veto`, `compaction_due` when one run would carry more than five items, and `export_error` and `folder_error` when a step after the approval failed |
57
+ | `approve(item_id, version, approver, labels=)` | Approves as proposed, or for the runs carrying `labels` when given, and `{}` for every run of the producer. `Approval` with `id`, `version`, `status`, `approver`, `veto`, `compaction_due` when one run would carry more than five items, and `export_error` and `folder_error` when a step after the approval failed |
58
58
  | `check_call(tool, arguments, producer)` | `Decision` with `action` of allow, block or ask, `veto` and `reason` |
59
59
 
60
60
  A tool that refuses, such as a proposal from a run nobody corrected, raises `ToolError` with the reason.
@@ -36,7 +36,7 @@ From then on every `knowledge("support-bot", acme)` carries the item, and a run
36
36
  | `record(producer, output, *, labels, applied, subject)` | the run id a verdict cites |
37
37
  | `judge(run, verdict, reason, reason_code, edited, reviewer)` | nothing |
38
38
  | `propose(kind, content, from_run, producer, labels, new_labels, item_id, veto)` | `Candidate` with `id`, `version`, `status` and `overlaps`, the current items of the same kind one run may carry with it |
39
- | `approve(item_id, version, approver)` | `Approval` with `id`, `version`, `status`, `approver`, `veto`, `compaction_due` when one run would carry more than five items, and `export_error` and `folder_error` when a step after the approval failed |
39
+ | `approve(item_id, version, approver, labels=)` | Approves as proposed, or for the runs carrying `labels` when given, and `{}` for every run of the producer. `Approval` with `id`, `version`, `status`, `approver`, `veto`, `compaction_due` when one run would carry more than five items, and `export_error` and `folder_error` when a step after the approval failed |
40
40
  | `check_call(tool, arguments, producer)` | `Decision` with `action` of allow, block or ask, `veto` and `reason` |
41
41
 
42
42
  A tool that refuses, such as a proposal from a run nobody corrected, raises `ToolError` with the reason.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "nodloop"
3
- version = "0.7.0"
3
+ version = "0.8.0"
4
4
  description = "Record AI runs and the verdicts people give on them, and hand the knowledge they approve to the next run in the same place"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -17,7 +17,7 @@ from . import binary
17
17
  __all__ = ["Approval", "Candidate", "Client", "Decision", "Item", "Knowledge", "ToolError"]
18
18
 
19
19
  # The binary release the client fetches when no nodloop is installed
20
- VERSION = "0.7.0"
20
+ VERSION = "0.8.0"
21
21
 
22
22
 
23
23
  class ToolError(RuntimeError):
@@ -225,9 +225,15 @@ class Client:
225
225
  answer = await self.call("propose", args)
226
226
  return Candidate(answer["id"], answer["version"], answer["status"], answer.get("overlaps") or [])
227
227
 
228
- async def approve(self, item_id: str, version: int, approver: str) -> Approval:
229
- """Approves a candidate on behalf of the named person"""
230
- answer = await self.call("approve", {"id": item_id, "version": version, "approver": approver})
228
+ async def approve(self, item_id: str, version: int, approver: str, *, labels: dict[str, list[str]] | None = None) -> Approval:
229
+ """Approves a candidate on behalf of the named person
230
+
231
+ labels approves it for the runs carrying them instead of the proposed scope and an empty mapping for every run of its producer
232
+ """
233
+ args: dict[str, Any] = {"id": item_id, "version": version, "approver": approver}
234
+ if labels is not None:
235
+ args["scope"] = {"labels": labels}
236
+ answer = await self.call("approve", args)
231
237
  return Approval(
232
238
  answer["id"],
233
239
  answer["version"],
@@ -96,3 +96,16 @@ async def test_approval_says_a_compaction_is_due(binary: str, records: str) -> N
96
96
 
97
97
  assert [a.compaction_due for a in approvals] == [False] * 5 + [True]
98
98
  assert all(a.folder_error == "" for a in approvals)
99
+
100
+
101
+ async def test_approve_in_picked_labels(binary: str, records: str) -> None:
102
+ async with Client(records, binary) as c:
103
+ run = await c.record("support-bot", "Here are the refund steps", labels=ACME)
104
+ await c.record("support-bot", "ok", labels={"tenant": ["globex"]})
105
+ await c.judge(run, "reject", reason="too long", reason_code="form")
106
+ proposed = await c.propose("judgment", "Answer in three lines", from_run=run)
107
+ approved = await c.approve(proposed.id, proposed.version, "ann", labels={})
108
+ globex = await c.knowledge("support-bot", {"tenant": ["globex"]})
109
+
110
+ assert approved.status == "approved"
111
+ assert [i.id for i in globex.items] == [proposed.id]
@@ -337,7 +337,7 @@ wheels = [
337
337
 
338
338
  [[package]]
339
339
  name = "claude-agent-sdk"
340
- version = "0.2.161"
340
+ version = "0.2.162"
341
341
  source = { registry = "https://pypi.org/simple" }
342
342
  dependencies = [
343
343
  { name = "anyio" },
@@ -346,12 +346,12 @@ dependencies = [
346
346
  { name = "sniffio" },
347
347
  { name = "typing-extensions", marker = "python_full_version < '3.11'" },
348
348
  ]
349
- sdist = { url = "https://files.pythonhosted.org/packages/8b/e1/8c94ff7f200eabcee9a9e57825dfac596bb6fbdeeafa594f49bc7e7f89b5/claude_agent_sdk-0.2.161.tar.gz", hash = "sha256:284a1372ec3af133578009ab2b821a238272dbf17d687a58be9df918d7b3ae89", size = 365168, upload-time = "2026-09-28T18:53:39.197Z" }
349
+ sdist = { url = "https://files.pythonhosted.org/packages/78/a3/d31886d0723593228797c96eb54d07a19e561931a2771ed40b4a105bedc9/claude_agent_sdk-0.2.162.tar.gz", hash = "sha256:e4cf92f04f934d3b87d42be9ecbeb75c1a3d487a8dc8a299f7fed1ad9d0b476f", size = 365167, upload-time = "2026-09-29T20:06:54.593Z" }
350
350
  wheels = [
351
- { url = "https://files.pythonhosted.org/packages/e8/be/528e3ec2bb30deff4fcc9484dd47d1022786ad3659d31110bd0ea66ea548/claude_agent_sdk-0.2.161-py3-none-macosx_11_0_arm64.whl", hash = "sha256:127c18a790dc9f77cc2d648f932e11b06cd3325c4b3de7a73d3bcf3d3b74b8bc", size = 93872213, upload-time = "2026-09-28T18:53:42.956Z" },
352
- { url = "https://files.pythonhosted.org/packages/17/1c/a0dcb628ac2f25bc92d858857a399202723098057828a909ca4f9893b721/claude_agent_sdk-0.2.161-py3-none-macosx_11_0_x86_64.whl", hash = "sha256:599e1c8ccb4d48ed57999b48e80937d6a6aefbc52b0c0cda1291aea21df29e3e", size = 98316495, upload-time = "2026-09-28T18:53:47.239Z" },
353
- { url = "https://files.pythonhosted.org/packages/73/04/3c0caee46c27eee75642b52ee11857c0d3b9d6917ecfd916da2ffc2c2d3f/claude_agent_sdk-0.2.161-py3-none-manylinux_2_17_aarch64.whl", hash = "sha256:18081d3277c07a71891bd15d1496387d9efc5f04a6b482ae3a03e7a0b8067069", size = 103331951, upload-time = "2026-09-28T18:53:51.367Z" },
354
- { url = "https://files.pythonhosted.org/packages/09/70/2c7c4160a19c6edd42e317bdc9cf810e91b6a535c04377fb09d4ee090769/claude_agent_sdk-0.2.161-py3-none-manylinux_2_17_x86_64.whl", hash = "sha256:b854e46d2889a51e1a77978711a05c757e63064bd5d8f89bcf74bb507babfc2c", size = 103684983, upload-time = "2026-09-28T18:53:55.496Z" },
351
+ { url = "https://files.pythonhosted.org/packages/6d/b6/ec37e32cb0782977e80cd00338f5073f150d6ecfa568e9fa1a1dd5c9eed3/claude_agent_sdk-0.2.162-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a0d5ad6185a8bcaaff05f5648036e2484cab28bd1c2ccc474fc8969555b386f7", size = 92700771, upload-time = "2026-09-29T20:06:59.661Z" },
352
+ { url = "https://files.pythonhosted.org/packages/92/c4/1165688243cbf90d3532f509ea65bc337c3ba7f8229e26d7297e00a6ec2b/claude_agent_sdk-0.2.162-py3-none-macosx_11_0_x86_64.whl", hash = "sha256:faab97b03f04f9c3deb58ad85a0410404d40b6efb5547c9defc4fa986642fced", size = 97130408, upload-time = "2026-09-29T20:07:06.243Z" },
353
+ { url = "https://files.pythonhosted.org/packages/05/41/f0addb831c3aefbc02c811ef7415e89e889d1bc2ae976eb935c508c91c0d/claude_agent_sdk-0.2.162-py3-none-manylinux_2_17_aarch64.whl", hash = "sha256:332cfb5eb9190fc8de1ad427753417d7c345264e9d2bd2ffa312afcc2f5cf766", size = 102183342, upload-time = "2026-09-29T20:07:14.279Z" },
354
+ { url = "https://files.pythonhosted.org/packages/d2/33/8a7acd75405d0cfde96c26219dbbac050aa23416ceefd5f62cf7aecfc22d/claude_agent_sdk-0.2.162-py3-none-manylinux_2_17_x86_64.whl", hash = "sha256:673598c9916f5f4d03ff49ab3128f856f0c6508135e8e258838fc3b6f7aafa49", size = 102525631, upload-time = "2026-09-29T20:07:23.411Z" },
355
355
  ]
356
356
 
357
357
  [[package]]
@@ -899,7 +899,7 @@ wheels = [
899
899
 
900
900
  [[package]]
901
901
  name = "nodloop"
902
- version = "0.7.0"
902
+ version = "0.8.0"
903
903
  source = { editable = "." }
904
904
  dependencies = [
905
905
  { name = "mcp" },
File without changes
File without changes
File without changes
File without changes
File without changes