levain-cli 0.1.14__tar.gz → 0.1.15__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.
Files changed (26) hide show
  1. {levain_cli-0.1.14 → levain_cli-0.1.15}/.gitignore +7 -0
  2. {levain_cli-0.1.14 → levain_cli-0.1.15}/PKG-INFO +1 -1
  3. {levain_cli-0.1.14 → levain_cli-0.1.15}/levain_recipe/models.py +4 -0
  4. {levain_cli-0.1.14 → levain_cli-0.1.15}/levain_recipe/validate.py +53 -1
  5. {levain_cli-0.1.14 → levain_cli-0.1.15}/pyproject.toml +1 -1
  6. {levain_cli-0.1.14 → levain_cli-0.1.15}/LICENSE +0 -0
  7. {levain_cli-0.1.14 → levain_cli-0.1.15}/README.md +0 -0
  8. {levain_cli-0.1.14 → levain_cli-0.1.15}/levain_recipe/__init__.py +0 -0
  9. {levain_cli-0.1.14 → levain_cli-0.1.15}/levain_recipe/__main__.py +0 -0
  10. {levain_cli-0.1.14 → levain_cli-0.1.15}/levain_recipe/_format.py +0 -0
  11. {levain_cli-0.1.14 → levain_cli-0.1.15}/levain_recipe/_java.py +0 -0
  12. {levain_cli-0.1.14 → levain_cli-0.1.15}/levain_recipe/auth.py +0 -0
  13. {levain_cli-0.1.14 → levain_cli-0.1.15}/levain_recipe/cli.py +0 -0
  14. {levain_cli-0.1.14 → levain_cli-0.1.15}/levain_recipe/codegen.py +0 -0
  15. {levain_cli-0.1.14 → levain_cli-0.1.15}/levain_recipe/graph.py +0 -0
  16. {levain_cli-0.1.14 → levain_cli-0.1.15}/levain_recipe/integrations_check.py +0 -0
  17. {levain_cli-0.1.14 → levain_cli-0.1.15}/levain_recipe/known_recipes.py +0 -0
  18. {levain_cli-0.1.14 → levain_cli-0.1.15}/levain_recipe/mcp.py +0 -0
  19. {levain_cli-0.1.14 → levain_cli-0.1.15}/levain_recipe/parser.py +0 -0
  20. {levain_cli-0.1.14 → levain_cli-0.1.15}/levain_recipe/profile.py +0 -0
  21. {levain_cli-0.1.14 → levain_cli-0.1.15}/levain_recipe/py.typed +0 -0
  22. {levain_cli-0.1.14 → levain_cli-0.1.15}/levain_recipe/reducers.py +0 -0
  23. {levain_cli-0.1.14 → levain_cli-0.1.15}/levain_recipe/source_check.py +0 -0
  24. {levain_cli-0.1.14 → levain_cli-0.1.15}/levain_recipe/tlc.py +0 -0
  25. {levain_cli-0.1.14 → levain_cli-0.1.15}/levain_recipe/translate.py +0 -0
  26. {levain_cli-0.1.14 → levain_cli-0.1.15}/levain_recipe/user_input.py +0 -0
@@ -18,6 +18,10 @@ build/
18
18
  !**/platform_agents/build/
19
19
  !**/src/build/
20
20
 
21
+ # Maven build output
22
+ services/flink/target/
23
+ services/flink/dependency-reduced-pom.xml
24
+
21
25
  # Binaries
22
26
  bin/
23
27
 
@@ -136,3 +140,6 @@ assets/video/*/renders/
136
140
  # www/public/videos/, the composition that made them does not.
137
141
  assets/video/updates-*/
138
142
  assets/video/*/snapshots/
143
+
144
+ # Maven build output (Flink job); the image build compiles from source.
145
+ services/flink/target/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: levain-cli
3
- Version: 0.1.14
3
+ Version: 0.1.15
4
4
  Summary: Levain recipe toolchain: PlusCal validator, TLC model checking, and GraphState codegen for agent recipes
5
5
  License-Expression: Apache-2.0
6
6
  License-File: LICENSE
@@ -134,6 +134,10 @@ class ValidatedSpec:
134
134
  routing: list[RoutingTable]
135
135
  invariants: list[str] = field(default_factory=list)
136
136
  constants: dict[str, Any] = field(default_factory=dict)
137
+ # Lanes the recipe splits its turns into (``\* @lanes [...]``):
138
+ # runs in different lanes may share the agent's workspace at the
139
+ # same time. Empty when the recipe runs one turn at a time.
140
+ lanes: list[str] = field(default_factory=list)
137
141
 
138
142
 
139
143
  @dataclass(frozen=True)
@@ -6,11 +6,12 @@ validation (PlusCal + Python source + pyproject.toml).
6
6
 
7
7
  from __future__ import annotations
8
8
 
9
+ import json
9
10
  import logging
10
11
  import os
11
12
  import re
12
13
  import shutil
13
- from typing import cast
14
+ from typing import Any, cast
14
15
 
15
16
  from ._java import resolve_jar
16
17
  from .graph import check_graph
@@ -52,6 +53,53 @@ _RE_COMMENT_PREFIX = re.compile(r"^\\[*]\s?")
52
53
  # excluded from the free-form description so the help text never
53
54
  # accidentally includes machine-readable metadata.
54
55
  _RE_ANNOTATION = re.compile(r"^\\[*]\s*@\w+")
56
+ # Module-level ``\* @lanes ["people"]``: the lanes whose turns may run
57
+ # at once on the agent's persistent workspace. ``people`` is the
58
+ # only lane a recipe can split out today; a trigger delivery or a
59
+ # scheduled fire stays in the default lane.
60
+ _RE_LANES = re.compile(r"^\s*\\[*]\s*@lanes\s+(\[.*\])\s*$")
61
+ KNOWN_LANES = frozenset({"people"})
62
+
63
+
64
+ def extract_lanes(source: str) -> list[str] | ValidationError:
65
+ r"""The lanes a ``\* @lanes`` line declares; ``[]`` without one."""
66
+ found = [
67
+ (n, m.group(1))
68
+ for n, line in enumerate(source.splitlines(), start=1)
69
+ if (m := _RE_LANES.match(line))
70
+ ]
71
+ if not found:
72
+ return []
73
+ line_no, raw = found[0]
74
+ lanes: Any = None
75
+ message: str | None
76
+ if len(found) > 1:
77
+ line_no, message = found[1][0], "@lanes may be declared once"
78
+ else:
79
+ try:
80
+ lanes = json.loads(raw)
81
+ except json.JSONDecodeError as e:
82
+ message = f"Invalid @lanes JSON: {e}"
83
+ else:
84
+ message = _lanes_problem(lanes)
85
+ if message:
86
+ return ValidationError(stage="profile", message=message, line=line_no)
87
+ return cast(list[str], lanes)
88
+
89
+
90
+ def _lanes_problem(lanes: Any) -> str | None:
91
+ if (
92
+ not isinstance(lanes, list)
93
+ or not lanes
94
+ or not all(isinstance(s, str) for s in lanes)
95
+ ):
96
+ return "@lanes must be a non-empty JSON list of strings"
97
+ unknown = sorted(set(lanes) - KNOWN_LANES)
98
+ if unknown:
99
+ return f"Unknown lane(s) {unknown}; known: {sorted(KNOWN_LANES)}"
100
+ if len(set(lanes)) != len(lanes):
101
+ return "@lanes repeats a lane"
102
+ return None
55
103
 
56
104
 
57
105
  def validate(
@@ -130,6 +178,9 @@ def validate_fast( # noqa: PLR0911
130
178
 
131
179
  constants = extract_constants(algo)
132
180
  invariants = [d for d in extract_defines(algo) if d not in constants]
181
+ lanes = extract_lanes(source)
182
+ if isinstance(lanes, ValidationError):
183
+ return [lanes]
133
184
 
134
185
  # Stage 3: Graph check. Tell the reachability check that
135
186
  # ``interrupt`` nodes count as termination — a chat-shape
@@ -177,6 +228,7 @@ def validate_fast( # noqa: PLR0911
177
228
  routing=routing,
178
229
  invariants=invariants,
179
230
  constants=constants,
231
+ lanes=lanes,
180
232
  )
181
233
 
182
234
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "levain-cli"
3
- version = "0.1.14"
3
+ version = "0.1.15"
4
4
  description = "Levain recipe toolchain: PlusCal validator, TLC model checking, and GraphState codegen for agent recipes"
5
5
  readme = "README.md"
6
6
  license = "Apache-2.0"
File without changes
File without changes