markov-protocols 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.
Files changed (30) hide show
  1. markov_protocols-0.1.0/LICENSE +21 -0
  2. markov_protocols-0.1.0/PKG-INFO +76 -0
  3. markov_protocols-0.1.0/README.md +51 -0
  4. markov_protocols-0.1.0/pyproject.toml +69 -0
  5. markov_protocols-0.1.0/src/markov_protocols/__init__.py +109 -0
  6. markov_protocols-0.1.0/src/markov_protocols/base.py +23 -0
  7. markov_protocols-0.1.0/src/markov_protocols/conditions/__init__.py +44 -0
  8. markov_protocols-0.1.0/src/markov_protocols/conditions/evaluate.py +129 -0
  9. markov_protocols-0.1.0/src/markov_protocols/conditions/models.py +111 -0
  10. markov_protocols-0.1.0/src/markov_protocols/definition/__init__.py +1 -0
  11. markov_protocols-0.1.0/src/markov_protocols/definition/metadata.py +30 -0
  12. markov_protocols-0.1.0/src/markov_protocols/definition/registry.py +54 -0
  13. markov_protocols-0.1.0/src/markov_protocols/definition/state.py +113 -0
  14. markov_protocols-0.1.0/src/markov_protocols/definition/states/__init__.py +9 -0
  15. markov_protocols-0.1.0/src/markov_protocols/definition/states/action_execute.py +48 -0
  16. markov_protocols-0.1.0/src/markov_protocols/definition/states/data_collection.py +27 -0
  17. markov_protocols-0.1.0/src/markov_protocols/definition/states/human_handoff.py +46 -0
  18. markov_protocols-0.1.0/src/markov_protocols/definition/transition.py +23 -0
  19. markov_protocols-0.1.0/src/markov_protocols/definition/workflow.py +101 -0
  20. markov_protocols-0.1.0/src/markov_protocols/py.typed +0 -0
  21. markov_protocols-0.1.0/src/markov_protocols/references.py +96 -0
  22. markov_protocols-0.1.0/src/markov_protocols/result.py +76 -0
  23. markov_protocols-0.1.0/src/markov_protocols/runtime/__init__.py +1 -0
  24. markov_protocols-0.1.0/src/markov_protocols/runtime/blackboard.py +55 -0
  25. markov_protocols-0.1.0/src/markov_protocols/runtime/events.py +73 -0
  26. markov_protocols-0.1.0/src/markov_protocols/runtime/session.py +325 -0
  27. markov_protocols-0.1.0/src/markov_protocols/serialization/__init__.py +83 -0
  28. markov_protocols-0.1.0/src/markov_protocols/serialization/converter.py +78 -0
  29. markov_protocols-0.1.0/src/markov_protocols/serialization/document.py +70 -0
  30. markov_protocols-0.1.0/src/markov_protocols/slug.py +21 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 gera
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,76 @@
1
+ Metadata-Version: 2.4
2
+ Name: markov-protocols
3
+ Version: 0.1.0
4
+ Summary: A deterministic finite state machine for modeling workflows that guide AI agents.
5
+ Keywords: finite-state-machine,fsm,workflow,state-machine,ai-agent,llm,deterministic
6
+ Author: geralm
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.13
14
+ Classifier: Programming Language :: Python :: 3.14
15
+ Classifier: Topic :: Software Development :: Libraries
16
+ Classifier: Typing :: Typed
17
+ Requires-Dist: pydantic>=2.13.4
18
+ Requires-Dist: pyyaml>=6.0 ; extra == 'yaml'
19
+ Requires-Python: >=3.13
20
+ Project-URL: Homepage, https://github.com/geralm/markov-protocols
21
+ Project-URL: Repository, https://github.com/geralm/markov-protocols
22
+ Project-URL: Issues, https://github.com/geralm/markov-protocols/issues
23
+ Provides-Extra: yaml
24
+ Description-Content-Type: text/markdown
25
+
26
+ # markov-protocols
27
+
28
+ A Finite State Machine capable of modeling workflows to provide guidance to an AI agent with determinism.
29
+
30
+ A standalone Python module providing the state-machine mechanism, meant to be reused across other Python projects.
31
+
32
+ ## Status
33
+
34
+ Slices 1–3 complete — the engine is functionally whole:
35
+
36
+ - **Definition** — conditions, references, metadata, the `State` interface with three concrete state
37
+ types, transitions, the registry, and `Workflow.compile()`.
38
+ - **Runtime** — `Blackboard`, the `Event` history, `Directive` materialization, and
39
+ `Session.start/update` with the write + fast-forward passes.
40
+ - **Integrity** — a correction (`ValueChanged`) reopens the states that depend on it, strict-rewinds
41
+ to the earliest one, clears reopened actions' outputs so they re-run, and flags `had_executed`
42
+ for host compensation. All derived from `history` — no parallel bookkeeping.
43
+
44
+ See [`docs/DESIGN.md`](docs/DESIGN.md) for the full architecture and build plan.
45
+
46
+ ## Development
47
+
48
+ Managed with [uv](https://docs.astral.sh/uv/).
49
+
50
+ ```bash
51
+ uv sync # create the environment and install dev tools
52
+ uv run pytest # run the test suite
53
+ uv run ruff check # lint
54
+ uv run mypy # type-check
55
+ ```
56
+
57
+ ## Layout
58
+
59
+ ```
60
+ src/markov_protocols/
61
+ __init__.py # curated public API
62
+ result.py # Result[T, E] / ErrorType — the error-handling pattern
63
+ base.py # StrictModel / StrictOpenModel
64
+ slug.py # deterministic title -> id
65
+ conditions/ # the typed boolean vocabulary + pure evaluator
66
+ references.py # Ref + resolve + referenced_fields
67
+ definition/ # metadata, State interface + concrete states, Transition,
68
+ # registry, Workflow.compile()
69
+ runtime/ # Blackboard, Event history, Session + update() pipeline
70
+ tests/ # one property-focused module per unit
71
+ ```
72
+
73
+ ## Conventions
74
+
75
+ - Operations return a `Result[T, E]` instead of raising exceptions.
76
+ - Target Python 3.13+.
@@ -0,0 +1,51 @@
1
+ # markov-protocols
2
+
3
+ A Finite State Machine capable of modeling workflows to provide guidance to an AI agent with determinism.
4
+
5
+ A standalone Python module providing the state-machine mechanism, meant to be reused across other Python projects.
6
+
7
+ ## Status
8
+
9
+ Slices 1–3 complete — the engine is functionally whole:
10
+
11
+ - **Definition** — conditions, references, metadata, the `State` interface with three concrete state
12
+ types, transitions, the registry, and `Workflow.compile()`.
13
+ - **Runtime** — `Blackboard`, the `Event` history, `Directive` materialization, and
14
+ `Session.start/update` with the write + fast-forward passes.
15
+ - **Integrity** — a correction (`ValueChanged`) reopens the states that depend on it, strict-rewinds
16
+ to the earliest one, clears reopened actions' outputs so they re-run, and flags `had_executed`
17
+ for host compensation. All derived from `history` — no parallel bookkeeping.
18
+
19
+ See [`docs/DESIGN.md`](docs/DESIGN.md) for the full architecture and build plan.
20
+
21
+ ## Development
22
+
23
+ Managed with [uv](https://docs.astral.sh/uv/).
24
+
25
+ ```bash
26
+ uv sync # create the environment and install dev tools
27
+ uv run pytest # run the test suite
28
+ uv run ruff check # lint
29
+ uv run mypy # type-check
30
+ ```
31
+
32
+ ## Layout
33
+
34
+ ```
35
+ src/markov_protocols/
36
+ __init__.py # curated public API
37
+ result.py # Result[T, E] / ErrorType — the error-handling pattern
38
+ base.py # StrictModel / StrictOpenModel
39
+ slug.py # deterministic title -> id
40
+ conditions/ # the typed boolean vocabulary + pure evaluator
41
+ references.py # Ref + resolve + referenced_fields
42
+ definition/ # metadata, State interface + concrete states, Transition,
43
+ # registry, Workflow.compile()
44
+ runtime/ # Blackboard, Event history, Session + update() pipeline
45
+ tests/ # one property-focused module per unit
46
+ ```
47
+
48
+ ## Conventions
49
+
50
+ - Operations return a `Result[T, E]` instead of raising exceptions.
51
+ - Target Python 3.13+.
@@ -0,0 +1,69 @@
1
+ [project]
2
+ name = "markov-protocols"
3
+ version = "0.1.0"
4
+ description = "A deterministic finite state machine for modeling workflows that guide AI agents."
5
+ readme = "README.md"
6
+ requires-python = ">=3.13"
7
+ license = "MIT"
8
+ license-files = ["LICENSE"]
9
+ authors = [
10
+ { name = "geralm" }
11
+ ]
12
+ keywords = [
13
+ "finite-state-machine",
14
+ "fsm",
15
+ "workflow",
16
+ "state-machine",
17
+ "ai-agent",
18
+ "llm",
19
+ "deterministic",
20
+ ]
21
+ classifiers = [
22
+ "Development Status :: 4 - Beta",
23
+ "Intended Audience :: Developers",
24
+ "Operating System :: OS Independent",
25
+ "Programming Language :: Python :: 3",
26
+ "Programming Language :: Python :: 3.13",
27
+ "Programming Language :: Python :: 3.14",
28
+ "Topic :: Software Development :: Libraries",
29
+ "Typing :: Typed",
30
+ ]
31
+ dependencies = [
32
+ "pydantic>=2.13.4",
33
+ ]
34
+
35
+ [project.optional-dependencies]
36
+ yaml = ["pyyaml>=6.0"]
37
+
38
+ [project.urls]
39
+ Homepage = "https://github.com/geralm/markov-protocols"
40
+ Repository = "https://github.com/geralm/markov-protocols"
41
+ Issues = "https://github.com/geralm/markov-protocols/issues"
42
+
43
+ [build-system]
44
+ requires = ["uv_build>=0.11.26,<0.12.0"]
45
+ build-backend = "uv_build"
46
+
47
+ [dependency-groups]
48
+ dev = [
49
+ "pytest>=8.0",
50
+ "ruff>=0.6",
51
+ "mypy>=1.11",
52
+ "pyyaml>=6.0",
53
+ "types-pyyaml>=6.0",
54
+ ]
55
+
56
+ [tool.pytest.ini_options]
57
+ testpaths = ["tests"]
58
+ addopts = "-ra"
59
+
60
+ [tool.ruff]
61
+ line-length = 100
62
+ src = ["src", "tests"]
63
+
64
+ [tool.ruff.lint]
65
+ select = ["E", "F", "I", "UP", "B"]
66
+
67
+ [tool.mypy]
68
+ strict = true
69
+ files = ["src"]
@@ -0,0 +1,109 @@
1
+ """markov-protocols: a deterministic finite state machine for modeling
2
+ workflows that guide AI agents.
3
+
4
+ Author with ``Workflow`` + the concrete state types, express logic with
5
+ ``Condition`` + ``Ref``, and handle outcomes with ``Result``. The runtime
6
+ (``Session``) arrives in a later slice.
7
+ """
8
+
9
+ from .conditions import (
10
+ All,
11
+ Any,
12
+ Condition,
13
+ Eq,
14
+ Exists,
15
+ Gt,
16
+ Gte,
17
+ In,
18
+ Lt,
19
+ Lte,
20
+ Ne,
21
+ Not,
22
+ Regex,
23
+ condition_fields,
24
+ evaluate,
25
+ )
26
+ from .definition.metadata import StateMetadata, TransitionMetadata
27
+ from .definition.registry import state_registry
28
+ from .definition.state import Directive, Requirement, State, Status
29
+ from .definition.states import ActionExecuteState, DataCollectionState, HumanHandoffState
30
+ from .definition.transition import Transition
31
+ from .definition.workflow import Workflow
32
+ from .references import Ref, referenced_fields, resolve
33
+ from .result import ErrorType, Result
34
+ from .runtime.blackboard import Blackboard
35
+ from .runtime.events import (
36
+ ActionExecuted,
37
+ Event,
38
+ StateCompleted,
39
+ StateReopened,
40
+ TransitionTaken,
41
+ ValueChanged,
42
+ ValueSet,
43
+ )
44
+ from .runtime.session import Session, UpdateResult
45
+ from .serialization import (
46
+ export_to_dict,
47
+ export_to_file,
48
+ from_json,
49
+ from_yaml,
50
+ import_from_dict,
51
+ import_from_file,
52
+ to_json,
53
+ to_yaml,
54
+ )
55
+ from .slug import slugify
56
+
57
+ __all__ = [
58
+ "ActionExecuteState",
59
+ "ActionExecuted",
60
+ "All",
61
+ "Any",
62
+ "Blackboard",
63
+ "Condition",
64
+ "DataCollectionState",
65
+ "Directive",
66
+ "Eq",
67
+ "ErrorType",
68
+ "Event",
69
+ "Exists",
70
+ "Gt",
71
+ "Gte",
72
+ "HumanHandoffState",
73
+ "In",
74
+ "Lt",
75
+ "Lte",
76
+ "Ne",
77
+ "Not",
78
+ "Ref",
79
+ "Regex",
80
+ "Requirement",
81
+ "Result",
82
+ "Session",
83
+ "State",
84
+ "StateCompleted",
85
+ "StateMetadata",
86
+ "StateReopened",
87
+ "Status",
88
+ "Transition",
89
+ "TransitionMetadata",
90
+ "TransitionTaken",
91
+ "UpdateResult",
92
+ "ValueChanged",
93
+ "ValueSet",
94
+ "Workflow",
95
+ "condition_fields",
96
+ "evaluate",
97
+ "export_to_dict",
98
+ "export_to_file",
99
+ "from_json",
100
+ "from_yaml",
101
+ "import_from_dict",
102
+ "import_from_file",
103
+ "referenced_fields",
104
+ "resolve",
105
+ "slugify",
106
+ "state_registry",
107
+ "to_json",
108
+ "to_yaml",
109
+ ]
@@ -0,0 +1,23 @@
1
+ """Shared Pydantic bases used across every layer.
2
+
3
+ ``StrictModel`` is the default: it rejects unknown fields and revalidates on
4
+ assignment, so a malformed definition can never silently exist. ``StrictOpenModel``
5
+ relaxes only the "unknown fields" rule — used for metadata, where we validate the
6
+ *shape* we rely on but let business policy attach anything else.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from pydantic import BaseModel, ConfigDict
12
+
13
+
14
+ class StrictModel(BaseModel):
15
+ """Reject unknown fields; validate on every assignment."""
16
+
17
+ model_config = ConfigDict(extra="forbid", validate_assignment=True)
18
+
19
+
20
+ class StrictOpenModel(BaseModel):
21
+ """Validate declared fields, but allow extra ones (extensible metadata)."""
22
+
23
+ model_config = ConfigDict(extra="allow", validate_assignment=True)
@@ -0,0 +1,44 @@
1
+ """Conditions: a small, closed, typed boolean vocabulary over the Blackboard.
2
+
3
+ A ``Condition`` reads collected data and returns a ``bool`` — the only way the
4
+ machine asks a yes/no question. The vocabulary is deliberately closed (not an open
5
+ expression language): it validates at authoring time, serializes to JSON, and is
6
+ safe to evaluate with no ``eval``.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from .evaluate import condition_fields, evaluate
12
+ from .models import (
13
+ All,
14
+ Any,
15
+ Condition,
16
+ Eq,
17
+ Exists,
18
+ Gt,
19
+ Gte,
20
+ In,
21
+ Lt,
22
+ Lte,
23
+ Ne,
24
+ Not,
25
+ Regex,
26
+ )
27
+
28
+ __all__ = [
29
+ "All",
30
+ "Any",
31
+ "Condition",
32
+ "Eq",
33
+ "Exists",
34
+ "Gt",
35
+ "Gte",
36
+ "In",
37
+ "Lt",
38
+ "Lte",
39
+ "Ne",
40
+ "Not",
41
+ "Regex",
42
+ "condition_fields",
43
+ "evaluate",
44
+ ]
@@ -0,0 +1,129 @@
1
+ """Pure evaluation of a ``Condition`` against collected data.
2
+
3
+ The evaluator is a dispatch table: ``op`` → a tiny pure function. Two invariants
4
+ hold for every leaf:
5
+
6
+ * A condition over a **missing** field is ``False`` — never an error. Absent data
7
+ can never assert anything, so a workflow can't be steered by data it doesn't have.
8
+ * Evaluation **never raises** on type mismatches (e.g. comparing a string to a
9
+ number) — it returns ``False``. Determinism must not depend on data being clean.
10
+
11
+ Only ``Not`` inverts, so ``Not(Exists("x"))`` is ``True`` when ``x`` is absent — the
12
+ one intentional way to assert on absence.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import re
18
+ from collections.abc import Callable, Mapping
19
+ from typing import Any
20
+
21
+ from .models import (
22
+ All,
23
+ Condition,
24
+ Eq,
25
+ Exists,
26
+ Gt,
27
+ Gte,
28
+ In,
29
+ Lt,
30
+ Lte,
31
+ Ne,
32
+ Not,
33
+ Regex,
34
+ )
35
+ from .models import (
36
+ Any as AnyCond,
37
+ )
38
+
39
+ _MISSING = object()
40
+
41
+
42
+ def evaluate(condition: Condition, data: Mapping[str, Any]) -> bool:
43
+ """Return whether ``condition`` holds for ``data``."""
44
+ return _DISPATCH[type(condition)](condition, data)
45
+
46
+
47
+ def condition_fields(condition: Condition) -> set[str]:
48
+ """Every Blackboard field name a condition tree references.
49
+
50
+ Used to report which data a state is still waiting on: the absent members of
51
+ this set are its missing fields.
52
+ """
53
+ if isinstance(condition, All | AnyCond):
54
+ if not condition.of:
55
+ return set()
56
+ return set().union(*(condition_fields(sub) for sub in condition.of))
57
+ if isinstance(condition, Not):
58
+ return condition_fields(condition.of)
59
+ return {condition.field}
60
+
61
+
62
+ def _get(data: Mapping[str, Any], field: str) -> Any:
63
+ return data.get(field, _MISSING)
64
+
65
+
66
+ def _exists(c: Exists, data: Mapping[str, Any]) -> bool:
67
+ value = _get(data, c.field)
68
+ return value is not _MISSING and value is not None
69
+
70
+
71
+ def _eq(c: Eq, data: Mapping[str, Any]) -> bool:
72
+ value = _get(data, c.field)
73
+ return value is not _MISSING and value == c.value
74
+
75
+
76
+ def _ne(c: Ne, data: Mapping[str, Any]) -> bool:
77
+ value = _get(data, c.field)
78
+ return value is not _MISSING and value != c.value
79
+
80
+
81
+ def _in(c: In, data: Mapping[str, Any]) -> bool:
82
+ value = _get(data, c.field)
83
+ return value is not _MISSING and value in c.value
84
+
85
+
86
+ def _regex(c: Regex, data: Mapping[str, Any]) -> bool:
87
+ value = _get(data, c.field)
88
+ return isinstance(value, str) and re.search(c.value, value) is not None
89
+
90
+
91
+ def _compare(op: Callable[[Any, Any], bool]) -> Callable[[Any, Mapping[str, Any]], bool]:
92
+ def run(c: Any, data: Mapping[str, Any]) -> bool:
93
+ value = _get(data, c.field)
94
+ if value is _MISSING:
95
+ return False
96
+ try:
97
+ return op(value, c.value)
98
+ except TypeError:
99
+ return False
100
+
101
+ return run
102
+
103
+
104
+ def _all(c: All, data: Mapping[str, Any]) -> bool:
105
+ return all(evaluate(sub, data) for sub in c.of)
106
+
107
+
108
+ def _any(c: AnyCond, data: Mapping[str, Any]) -> bool:
109
+ return any(evaluate(sub, data) for sub in c.of)
110
+
111
+
112
+ def _not(c: Not, data: Mapping[str, Any]) -> bool:
113
+ return not evaluate(c.of, data)
114
+
115
+
116
+ _DISPATCH: dict[type, Callable[[Any, Mapping[str, Any]], bool]] = {
117
+ Exists: _exists,
118
+ Eq: _eq,
119
+ Ne: _ne,
120
+ In: _in,
121
+ Regex: _regex,
122
+ Gt: _compare(lambda a, b: a > b),
123
+ Gte: _compare(lambda a, b: a >= b),
124
+ Lt: _compare(lambda a, b: a < b),
125
+ Lte: _compare(lambda a, b: a <= b),
126
+ All: _all,
127
+ AnyCond: _any,
128
+ Not: _not,
129
+ }
@@ -0,0 +1,111 @@
1
+ """The ``Condition`` discriminated union.
2
+
3
+ Each condition is a small immutable value object tagged by ``op``. Leaf conditions
4
+ test one field; combinators (``all``/``any``/``not``) nest other conditions. The
5
+ union is discriminated on ``op`` so it round-trips cleanly through JSON.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import Annotated, Literal
11
+ from typing import Any as AnyType
12
+
13
+ from pydantic import Field
14
+
15
+ from ..base import StrictModel
16
+
17
+
18
+ class Exists(StrictModel):
19
+ """True when ``field`` is present and not null."""
20
+
21
+ op: Literal["exists"] = "exists"
22
+ field: str
23
+
24
+
25
+ class Eq(StrictModel):
26
+ """True when ``field`` exists and equals ``value``."""
27
+
28
+ op: Literal["eq"] = "eq"
29
+ field: str
30
+ value: AnyType
31
+
32
+
33
+ class Ne(StrictModel):
34
+ """True when ``field`` exists and does not equal ``value`` (missing → False)."""
35
+
36
+ op: Literal["ne"] = "ne"
37
+ field: str
38
+ value: AnyType
39
+
40
+
41
+ class In(StrictModel):
42
+ """True when ``field`` exists and its value is one of ``value``."""
43
+
44
+ op: Literal["in"] = "in"
45
+ field: str
46
+ value: list[AnyType]
47
+
48
+
49
+ class Regex(StrictModel):
50
+ """True when ``field`` is a string that ``value`` (a regex) finds a match in."""
51
+
52
+ op: Literal["regex"] = "regex"
53
+ field: str
54
+ value: str
55
+
56
+
57
+ class Gt(StrictModel):
58
+ op: Literal["gt"] = "gt"
59
+ field: str
60
+ value: AnyType
61
+
62
+
63
+ class Gte(StrictModel):
64
+ op: Literal["gte"] = "gte"
65
+ field: str
66
+ value: AnyType
67
+
68
+
69
+ class Lt(StrictModel):
70
+ op: Literal["lt"] = "lt"
71
+ field: str
72
+ value: AnyType
73
+
74
+
75
+ class Lte(StrictModel):
76
+ op: Literal["lte"] = "lte"
77
+ field: str
78
+ value: AnyType
79
+
80
+
81
+ class All(StrictModel):
82
+ """True when every nested condition is true (vacuously true when empty)."""
83
+
84
+ op: Literal["all"] = "all"
85
+ of: list[Condition]
86
+
87
+
88
+ class Any(StrictModel):
89
+ """True when at least one nested condition is true (false when empty)."""
90
+
91
+ op: Literal["any"] = "any"
92
+ of: list[Condition]
93
+
94
+
95
+ class Not(StrictModel):
96
+ """The negation of a nested condition."""
97
+
98
+ op: Literal["not"] = "not"
99
+ of: Condition
100
+
101
+
102
+ Condition = Annotated[
103
+ Exists | Eq | Ne | In | Regex | Gt | Gte | Lt | Lte | All | Any | Not,
104
+ Field(discriminator="op"),
105
+ ]
106
+ """Any boolean rule over the Blackboard."""
107
+
108
+ # Resolve the forward references inside the recursive combinators.
109
+ All.model_rebuild()
110
+ Any.model_rebuild()
111
+ Not.model_rebuild()
@@ -0,0 +1 @@
1
+ """The definition layer: the immutable, serializable models a user authors."""
@@ -0,0 +1,30 @@
1
+ """State and transition metadata: typed-but-open policy carriers.
2
+
3
+ The reserved sections are the shape the machine relies on being *present*; their
4
+ contents are opaque business policy the machine never interprets. ``extra="allow"``
5
+ (via ``StrictOpenModel``) lets authors attach anything beyond the reserved keys.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import Any
11
+
12
+ from pydantic import Field
13
+
14
+ from ..base import StrictOpenModel
15
+
16
+
17
+ class StateMetadata(StrictOpenModel):
18
+ """Policy attached to a state. Contents are opaque to the machine."""
19
+
20
+ tools: dict[str, Any] = Field(default_factory=dict)
21
+ prompts: dict[str, Any] = Field(default_factory=dict)
22
+ guardrails: dict[str, Any] = Field(default_factory=dict)
23
+
24
+
25
+ class TransitionMetadata(StrictOpenModel):
26
+ """Policy attached to a transition. Contents are opaque to the machine."""
27
+
28
+ events: dict[str, Any] = Field(default_factory=dict)
29
+ resources: dict[str, Any] = Field(default_factory=dict)
30
+ reward: float = Field(default=1.0, ge=0.0, description="Transition reward (experimental)")