relayr-ai 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 (50) hide show
  1. relayr_ai-0.1.0/LICENSE +21 -0
  2. relayr_ai-0.1.0/PKG-INFO +106 -0
  3. relayr_ai-0.1.0/README.md +54 -0
  4. relayr_ai-0.1.0/pyproject.toml +166 -0
  5. relayr_ai-0.1.0/pyproject.toml.orig +133 -0
  6. relayr_ai-0.1.0/src/relayr/__init__.py +20 -0
  7. relayr_ai-0.1.0/src/relayr/evals/__init__.py +32 -0
  8. relayr_ai-0.1.0/src/relayr/evals/measures.py +145 -0
  9. relayr_ai-0.1.0/src/relayr/evals/outcomes.py +132 -0
  10. relayr_ai-0.1.0/src/relayr/evals/revisions.py +56 -0
  11. relayr_ai-0.1.0/src/relayr/events/__init__.py +40 -0
  12. relayr_ai-0.1.0/src/relayr/events/projections.py +155 -0
  13. relayr_ai-0.1.0/src/relayr/events/types.py +139 -0
  14. relayr_ai-0.1.0/src/relayr/inbound/__init__.py +16 -0
  15. relayr_ai-0.1.0/src/relayr/inbound/followers.py +392 -0
  16. relayr_ai-0.1.0/src/relayr/langfuse/__init__.py +13 -0
  17. relayr_ai-0.1.0/src/relayr/langfuse/tracing.py +85 -0
  18. relayr_ai-0.1.0/src/relayr/ledger/__init__.py +29 -0
  19. relayr_ai-0.1.0/src/relayr/ledger/ledger.py +113 -0
  20. relayr_ai-0.1.0/src/relayr/ledger/memory.py +86 -0
  21. relayr_ai-0.1.0/src/relayr/otel/__init__.py +15 -0
  22. relayr_ai-0.1.0/src/relayr/otel/contribution.py +43 -0
  23. relayr_ai-0.1.0/src/relayr/outbound/__init__.py +44 -0
  24. relayr_ai-0.1.0/src/relayr/outbound/actions.py +495 -0
  25. relayr_ai-0.1.0/src/relayr/outbound/allowlist.py +34 -0
  26. relayr_ai-0.1.0/src/relayr/outbound/capability.py +49 -0
  27. relayr_ai-0.1.0/src/relayr/outbound/tools.py +134 -0
  28. relayr_ai-0.1.0/src/relayr/outbound/turns.py +29 -0
  29. relayr_ai-0.1.0/src/relayr/py.typed +0 -0
  30. relayr_ai-0.1.0/src/relayr/rules/__init__.py +37 -0
  31. relayr_ai-0.1.0/src/relayr/rules/drafting.py +147 -0
  32. relayr_ai-0.1.0/src/relayr/rules/drafts.py +44 -0
  33. relayr_ai-0.1.0/src/relayr/rules/installer.py +333 -0
  34. relayr_ai-0.1.0/src/relayr/rules/preview.py +74 -0
  35. relayr_ai-0.1.0/src/relayr/sql/__init__.py +32 -0
  36. relayr_ai-0.1.0/src/relayr/sql/ledger.py +226 -0
  37. relayr_ai-0.1.0/src/relayr/sql/migrations/env.py +17 -0
  38. relayr_ai-0.1.0/src/relayr/sql/migrations/script.py.mako +26 -0
  39. relayr_ai-0.1.0/src/relayr/sql/migrations/versions/0001_initial.py +47 -0
  40. relayr_ai-0.1.0/src/relayr/sql/migrations/versions/0002_causes.py +39 -0
  41. relayr_ai-0.1.0/src/relayr/sql/migrations/versions/0003_causes_recorded_at.py +47 -0
  42. relayr_ai-0.1.0/src/relayr/sql/migrations/versions/0004_row_level_security.py +50 -0
  43. relayr_ai-0.1.0/src/relayr/sql/schema.py +65 -0
  44. relayr_ai-0.1.0/src/relayr/sql/sqlite.py +18 -0
  45. relayr_ai-0.1.0/src/relayr/sql/tables.py +77 -0
  46. relayr_ai-0.1.0/src/relayr/sql/tenancy.py +18 -0
  47. relayr_ai-0.1.0/src/relayr/telemetry/__init__.py +34 -0
  48. relayr_ai-0.1.0/src/relayr/telemetry/attributes.py +14 -0
  49. relayr_ai-0.1.0/src/relayr/telemetry/metrics.py +48 -0
  50. relayr_ai-0.1.0/src/relayr/telemetry/telemetry.py +60 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alex Nodeland
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,106 @@
1
+ Metadata-Version: 2.4
2
+ Name: relayr-ai
3
+ Version: 0.1.0
4
+ Summary: The bridge between artifactr and reflexr: events from chats and artifacts become events for rules, and runs act back in the chat as proposals and notices.
5
+ Keywords: agents,llm,events,rules,collaboration,artifacts,pydantic-ai
6
+ Author: Alex Nodeland
7
+ Author-email: Alex Nodeland <alex@ournature.studio>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 2 - Pre-Alpha
11
+ Classifier: Framework :: Pydantic :: 2
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3 :: Only
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Programming Language :: Python :: 3.14
18
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
19
+ Classifier: Typing :: Typed
20
+ Requires-Dist: artifactr-ai>=0.1.0
21
+ Requires-Dist: reflexr>=0.1.0.dev0
22
+ Requires-Dist: pydantic>=2.13
23
+ Requires-Dist: pydantic-ai-slim>=2.51
24
+ Requires-Dist: opentelemetry-api>=1.45
25
+ Requires-Dist: evalr>=0.1.0.dev0 ; extra == 'evals'
26
+ Requires-Dist: langfuse>=4.15,<5 ; extra == 'langfuse'
27
+ Requires-Dist: artifactr-ai[langfuse]>=0.1.0 ; extra == 'langfuse'
28
+ Requires-Dist: reflexr[langfuse]>=0.1.0.dev0 ; extra == 'langfuse'
29
+ Requires-Dist: evalr[langfuse]>=0.1.0.dev0 ; extra == 'langfuse'
30
+ Requires-Dist: lattice-core-ai>=0.1.0 ; extra == 'otel'
31
+ Requires-Dist: opentelemetry-sdk>=1.45 ; extra == 'otel'
32
+ Requires-Dist: relayr-ai[sql] ; extra == 'postgres'
33
+ Requires-Dist: asyncpg>=0.31 ; extra == 'postgres'
34
+ Requires-Dist: lattice-core-ai[sql]>=0.1.0 ; extra == 'sql'
35
+ Requires-Dist: sqlalchemy[asyncio]>=2.1 ; extra == 'sql'
36
+ Requires-Dist: alembic>=1.20 ; extra == 'sql'
37
+ Requires-Dist: relayr-ai[sql] ; extra == 'sqlite'
38
+ Requires-Dist: aiosqlite>=0.22 ; extra == 'sqlite'
39
+ Requires-Python: >=3.12
40
+ Project-URL: Homepage, https://lattice.alexnodeland.com/relayr/
41
+ Project-URL: Documentation, https://lattice.alexnodeland.com/relayr/
42
+ Project-URL: Repository, https://github.com/alexnodeland/lattice
43
+ Project-URL: Issues, https://github.com/alexnodeland/lattice/issues
44
+ Project-URL: Changelog, https://github.com/alexnodeland/lattice/blob/main/packages/relayr/CHANGELOG.md
45
+ Provides-Extra: evals
46
+ Provides-Extra: langfuse
47
+ Provides-Extra: otel
48
+ Provides-Extra: postgres
49
+ Provides-Extra: sql
50
+ Provides-Extra: sqlite
51
+ Description-Content-Type: text/markdown
52
+
53
+ <picture>
54
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/alexnodeland/lattice/main/docs/relayr/assets/brand/banner-dark.svg">
55
+ <img alt="relayr: the bridge between artifactr and reflexr." src="https://raw.githubusercontent.com/alexnodeland/lattice/main/docs/relayr/assets/brand/banner-light.svg" width="100%">
56
+ </picture>
57
+
58
+ <p>
59
+ <a href="https://lattice.alexnodeland.com/relayr/"><img alt="Docs" src="https://img.shields.io/badge/docs-lattice.alexnodeland.com%2Frelayr-9E0A5E"></a>
60
+ <a href="https://github.com/alexnodeland/lattice/actions/workflows/nightly.yml"><img alt="Nightly" src="https://github.com/alexnodeland/lattice/actions/workflows/nightly.yml/badge.svg?branch=main"></a>
61
+ <img alt="Python 3.12, 3.13 and 3.14" src="https://img.shields.io/badge/python-3.12%20%7C%203.13%20%7C%203.14-9E0A5E">
62
+ <img alt="Coverage: 100%" src="https://img.shields.io/badge/coverage-100%25-9E0A5E">
63
+ <img alt="Typed: pyright strict" src="https://img.shields.io/badge/typed-pyright%20strict-9E0A5E">
64
+ <a href="https://github.com/alexnodeland/lattice/blob/main/packages/relayr/LICENSE"><img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-9E0A5E"></a>
65
+ </p>
66
+
67
+ **relayr** is the bridge between [artifactr](https://lattice.alexnodeland.com/artifactr/) and [reflexr](https://lattice.alexnodeland.com/reflexr/): events from chats and artifacts become events for rules, and runs act back in the chat as proposals and notices. It uses only the two libraries' public APIs, and neither library imports it or the other.
68
+
69
+ > **Status:** built: the [v0.1 plan](https://lattice.alexnodeland.com/relayr/rfcs/0001-v0.1-implementation-plan/). Both adapters, telemetry across the bridge, rules from chat and evaluation, and stackr's template wires it all into an application. Its distribution is `relayr-ai`, since the name `relayr` on PyPI is an unrelated project, and it is imported as `relayr`.
70
+
71
+ ## Why
72
+
73
+ An application can mount artifactr and reflexr side by side, on one database and one telemetry setup, over one shared context: the same tenant and workspace ids name the same context in both. But the two halves don't talk to each other. A rule can't see what people say in a thread, and a run can't put its result in front of the people it concerns. Each application that wants this would write its own glue, and meet the same hard parts: duplicate and lost deliveries, rules that trigger themselves, two kinds of session that don't line up, and events that anyone could forge. [stackr's RFC-0002](https://lattice.alexnodeland.com/stackr/rfcs/0002-the-combined-system/) designs the bridge once, and relayr is where it is built.
74
+
75
+ ## What it does
76
+
77
+ - **Inbound: chats and artifacts become events (built).** One follower per workspace, with a lease, a cursor and dead letters, publishes selected artifactr events into reflexr under the `artifactr` namespace, such as `artifactr:message_posted`, `artifactr:artifact_changed` and `artifactr:proposal_resolved`, so that a message fires a rule exactly once, across a restart and a redelivery ([ADR-0006](https://lattice.alexnodeland.com/relayr/adr/0006-bridged-types-in-the-artifactr-namespace/)).
78
+ - **Outbound: runs act as participants (built).** A pydantic-ai capability and helpers let a run act in artifactr as its rule's own external agent, `reflexr:<rule>`, so artifactr's rules apply to it: under a `propose` write policy its edits are proposals, and relayr never resolves one ([ADR-0002](https://lattice.alexnodeland.com/relayr/adr/0002-the-outbound-actor-an-external-agent-per-rule/)).
79
+ - **Notices by default (built).** A rule's messages start no turn in the thread, unless the rule's allowlist grants it ([ADR-0007](https://lattice.alexnodeland.com/relayr/adr/0007-notices-by-default/)).
80
+ - **Chains and sessions kept apart, and linked (built).** A thread is never a causal chain, and a chain never joins a thread's session or model history; traces are linked, and Langfuse sessions tagged, across the two ([ADR-0003](https://lattice.alexnodeland.com/relayr/adr/0003-a-thread-is-never-a-chain/), [ADR-0004](https://lattice.alexnodeland.com/relayr/adr/0004-sessions-linked-not-continued/)).
81
+ - **Rules from chat (built).** A rule drafted in a thread is a `rule` artifact, reviewed as a proposal, and goes live in reflexr's stored rules only when a person who may install rules accepts it, with provenance recorded both ways. A rule changed in reflexr directly is proposed back to its artifact ([ADR-0005](https://lattice.alexnodeland.com/relayr/adr/0005-where-a-rules-source-of-truth-lives/)).
82
+ - **Evaluation (built).** Proposal outcomes become feedback on the runs that proposed them, with measures of acceptance, rewrites and rules from chat that stay live, in the `evals` extra.
83
+ - **A ledger of its own (built)**, in memory or in SQL, behind the `sql`, `sqlite` and `postgres` extras.
84
+
85
+ ## Documentation
86
+
87
+ relayr's documentation is its section of lattice's site, at **<https://lattice.alexnodeland.com/relayr/>**, built from [`docs/relayr/`](https://github.com/alexnodeland/lattice/tree/main/docs/relayr).
88
+
89
+ - [Bridging artifactr into reflexr](https://lattice.alexnodeland.com/relayr/guides/inbound/): wiring the followers into an application.
90
+ - [Acting in artifactr from rules](https://lattice.alexnodeland.com/relayr/guides/outbound/): the outbound adapter, allowlists and loops.
91
+ - [Tracing across the bridge](https://lattice.alexnodeland.com/relayr/guides/telemetry/): links, Langfuse tags and counters.
92
+ - [Rules from chat](https://lattice.alexnodeland.com/relayr/guides/rules/): drafting, previewing and installing rules, and drift.
93
+ - [Evaluating the bridge's rules](https://lattice.alexnodeland.com/relayr/guides/evaluation/): proposal outcomes and the measures.
94
+ - [Architecture](https://lattice.alexnodeland.com/relayr/architecture/): what exists, and how it works.
95
+ - [Architecture decision records](https://lattice.alexnodeland.com/relayr/adr/): why each part is the way it is.
96
+ - [RFCs](https://lattice.alexnodeland.com/relayr/rfcs/): the v0.1 plan, its phases and what each waits on.
97
+ - [The combined system](https://lattice.alexnodeland.com/stackr/rfcs/0002-the-combined-system/), stackr's RFC-0002: the design relayr builds.
98
+ - [Brand](https://lattice.alexnodeland.com/relayr/assets/brand/): the mark and its colours.
99
+
100
+ ## Contributing
101
+
102
+ See [CONTRIBUTING.md](https://github.com/alexnodeland/lattice/blob/main/CONTRIBUTING.md) for setup, the trunk-based workflow, and the RFC and ADR process.
103
+
104
+ ## License
105
+
106
+ [MIT](https://github.com/alexnodeland/lattice/blob/main/packages/relayr/LICENSE)
@@ -0,0 +1,54 @@
1
+ <picture>
2
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/alexnodeland/lattice/main/docs/relayr/assets/brand/banner-dark.svg">
3
+ <img alt="relayr: the bridge between artifactr and reflexr." src="https://raw.githubusercontent.com/alexnodeland/lattice/main/docs/relayr/assets/brand/banner-light.svg" width="100%">
4
+ </picture>
5
+
6
+ <p>
7
+ <a href="https://lattice.alexnodeland.com/relayr/"><img alt="Docs" src="https://img.shields.io/badge/docs-lattice.alexnodeland.com%2Frelayr-9E0A5E"></a>
8
+ <a href="https://github.com/alexnodeland/lattice/actions/workflows/nightly.yml"><img alt="Nightly" src="https://github.com/alexnodeland/lattice/actions/workflows/nightly.yml/badge.svg?branch=main"></a>
9
+ <img alt="Python 3.12, 3.13 and 3.14" src="https://img.shields.io/badge/python-3.12%20%7C%203.13%20%7C%203.14-9E0A5E">
10
+ <img alt="Coverage: 100%" src="https://img.shields.io/badge/coverage-100%25-9E0A5E">
11
+ <img alt="Typed: pyright strict" src="https://img.shields.io/badge/typed-pyright%20strict-9E0A5E">
12
+ <a href="https://github.com/alexnodeland/lattice/blob/main/packages/relayr/LICENSE"><img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-9E0A5E"></a>
13
+ </p>
14
+
15
+ **relayr** is the bridge between [artifactr](https://lattice.alexnodeland.com/artifactr/) and [reflexr](https://lattice.alexnodeland.com/reflexr/): events from chats and artifacts become events for rules, and runs act back in the chat as proposals and notices. It uses only the two libraries' public APIs, and neither library imports it or the other.
16
+
17
+ > **Status:** built: the [v0.1 plan](https://lattice.alexnodeland.com/relayr/rfcs/0001-v0.1-implementation-plan/). Both adapters, telemetry across the bridge, rules from chat and evaluation, and stackr's template wires it all into an application. Its distribution is `relayr-ai`, since the name `relayr` on PyPI is an unrelated project, and it is imported as `relayr`.
18
+
19
+ ## Why
20
+
21
+ An application can mount artifactr and reflexr side by side, on one database and one telemetry setup, over one shared context: the same tenant and workspace ids name the same context in both. But the two halves don't talk to each other. A rule can't see what people say in a thread, and a run can't put its result in front of the people it concerns. Each application that wants this would write its own glue, and meet the same hard parts: duplicate and lost deliveries, rules that trigger themselves, two kinds of session that don't line up, and events that anyone could forge. [stackr's RFC-0002](https://lattice.alexnodeland.com/stackr/rfcs/0002-the-combined-system/) designs the bridge once, and relayr is where it is built.
22
+
23
+ ## What it does
24
+
25
+ - **Inbound: chats and artifacts become events (built).** One follower per workspace, with a lease, a cursor and dead letters, publishes selected artifactr events into reflexr under the `artifactr` namespace, such as `artifactr:message_posted`, `artifactr:artifact_changed` and `artifactr:proposal_resolved`, so that a message fires a rule exactly once, across a restart and a redelivery ([ADR-0006](https://lattice.alexnodeland.com/relayr/adr/0006-bridged-types-in-the-artifactr-namespace/)).
26
+ - **Outbound: runs act as participants (built).** A pydantic-ai capability and helpers let a run act in artifactr as its rule's own external agent, `reflexr:<rule>`, so artifactr's rules apply to it: under a `propose` write policy its edits are proposals, and relayr never resolves one ([ADR-0002](https://lattice.alexnodeland.com/relayr/adr/0002-the-outbound-actor-an-external-agent-per-rule/)).
27
+ - **Notices by default (built).** A rule's messages start no turn in the thread, unless the rule's allowlist grants it ([ADR-0007](https://lattice.alexnodeland.com/relayr/adr/0007-notices-by-default/)).
28
+ - **Chains and sessions kept apart, and linked (built).** A thread is never a causal chain, and a chain never joins a thread's session or model history; traces are linked, and Langfuse sessions tagged, across the two ([ADR-0003](https://lattice.alexnodeland.com/relayr/adr/0003-a-thread-is-never-a-chain/), [ADR-0004](https://lattice.alexnodeland.com/relayr/adr/0004-sessions-linked-not-continued/)).
29
+ - **Rules from chat (built).** A rule drafted in a thread is a `rule` artifact, reviewed as a proposal, and goes live in reflexr's stored rules only when a person who may install rules accepts it, with provenance recorded both ways. A rule changed in reflexr directly is proposed back to its artifact ([ADR-0005](https://lattice.alexnodeland.com/relayr/adr/0005-where-a-rules-source-of-truth-lives/)).
30
+ - **Evaluation (built).** Proposal outcomes become feedback on the runs that proposed them, with measures of acceptance, rewrites and rules from chat that stay live, in the `evals` extra.
31
+ - **A ledger of its own (built)**, in memory or in SQL, behind the `sql`, `sqlite` and `postgres` extras.
32
+
33
+ ## Documentation
34
+
35
+ relayr's documentation is its section of lattice's site, at **<https://lattice.alexnodeland.com/relayr/>**, built from [`docs/relayr/`](https://github.com/alexnodeland/lattice/tree/main/docs/relayr).
36
+
37
+ - [Bridging artifactr into reflexr](https://lattice.alexnodeland.com/relayr/guides/inbound/): wiring the followers into an application.
38
+ - [Acting in artifactr from rules](https://lattice.alexnodeland.com/relayr/guides/outbound/): the outbound adapter, allowlists and loops.
39
+ - [Tracing across the bridge](https://lattice.alexnodeland.com/relayr/guides/telemetry/): links, Langfuse tags and counters.
40
+ - [Rules from chat](https://lattice.alexnodeland.com/relayr/guides/rules/): drafting, previewing and installing rules, and drift.
41
+ - [Evaluating the bridge's rules](https://lattice.alexnodeland.com/relayr/guides/evaluation/): proposal outcomes and the measures.
42
+ - [Architecture](https://lattice.alexnodeland.com/relayr/architecture/): what exists, and how it works.
43
+ - [Architecture decision records](https://lattice.alexnodeland.com/relayr/adr/): why each part is the way it is.
44
+ - [RFCs](https://lattice.alexnodeland.com/relayr/rfcs/): the v0.1 plan, its phases and what each waits on.
45
+ - [The combined system](https://lattice.alexnodeland.com/stackr/rfcs/0002-the-combined-system/), stackr's RFC-0002: the design relayr builds.
46
+ - [Brand](https://lattice.alexnodeland.com/relayr/assets/brand/): the mark and its colours.
47
+
48
+ ## Contributing
49
+
50
+ See [CONTRIBUTING.md](https://github.com/alexnodeland/lattice/blob/main/CONTRIBUTING.md) for setup, the trunk-based workflow, and the RFC and ADR process.
51
+
52
+ ## License
53
+
54
+ [MIT](https://github.com/alexnodeland/lattice/blob/main/packages/relayr/LICENSE)
@@ -0,0 +1,166 @@
1
+ [project]
2
+ name = "relayr-ai"
3
+ version = "0.1.0"
4
+ description = "The bridge between artifactr and reflexr: events from chats and artifacts become events for rules, and runs act back in the chat as proposals and notices."
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ license-files = ["LICENSE"]
8
+ requires-python = ">=3.12"
9
+ keywords = [
10
+ "agents",
11
+ "llm",
12
+ "events",
13
+ "rules",
14
+ "collaboration",
15
+ "artifacts",
16
+ "pydantic-ai",
17
+ ]
18
+ classifiers = [
19
+ "Development Status :: 2 - Pre-Alpha",
20
+ "Framework :: Pydantic :: 2",
21
+ "Intended Audience :: Developers",
22
+ "Operating System :: OS Independent",
23
+ "Programming Language :: Python :: 3 :: Only",
24
+ "Programming Language :: Python :: 3.12",
25
+ "Programming Language :: Python :: 3.13",
26
+ "Programming Language :: Python :: 3.14",
27
+ "Topic :: Software Development :: Libraries :: Application Frameworks",
28
+ "Typing :: Typed",
29
+ ]
30
+ dependencies = [
31
+ "artifactr-ai>=0.1.0",
32
+ "reflexr>=0.1.0.dev0",
33
+ "pydantic>=2.13",
34
+ "pydantic-ai-slim>=2.51",
35
+ "opentelemetry-api>=1.45",
36
+ ]
37
+
38
+ [[project.authors]]
39
+ name = "Alex Nodeland"
40
+ email = "alex@ournature.studio"
41
+
42
+ [project.optional-dependencies]
43
+ sql = [
44
+ "lattice-core-ai[sql]>=0.1.0",
45
+ "sqlalchemy[asyncio]>=2.1",
46
+ "alembic>=1.20",
47
+ ]
48
+ sqlite = [
49
+ "relayr-ai[sql]",
50
+ "aiosqlite>=0.22",
51
+ ]
52
+ postgres = [
53
+ "relayr-ai[sql]",
54
+ "asyncpg>=0.31",
55
+ ]
56
+ otel = [
57
+ "lattice-core-ai>=0.1.0",
58
+ "opentelemetry-sdk>=1.45",
59
+ ]
60
+ evals = ["evalr>=0.1.0.dev0"]
61
+ langfuse = [
62
+ "langfuse>=4.15,<5",
63
+ "artifactr-ai[langfuse]>=0.1.0",
64
+ "reflexr[langfuse]>=0.1.0.dev0",
65
+ "evalr[langfuse]>=0.1.0.dev0",
66
+ ]
67
+
68
+ [project.urls]
69
+ Homepage = "https://lattice.alexnodeland.com/relayr/"
70
+ Documentation = "https://lattice.alexnodeland.com/relayr/"
71
+ Repository = "https://github.com/alexnodeland/lattice"
72
+ Issues = "https://github.com/alexnodeland/lattice/issues"
73
+ Changelog = "https://github.com/alexnodeland/lattice/blob/main/packages/relayr/CHANGELOG.md"
74
+
75
+ [dependency-groups]
76
+ test = [
77
+ "pytest>=9.1",
78
+ "pytest-asyncio>=1.4",
79
+ "pytest-cov>=7.1",
80
+ "pytest-timeout>=2.4",
81
+ "aiosqlite>=0.22",
82
+ "asyncpg>=0.31",
83
+ ]
84
+ lint = [
85
+ "ruff>=0.16",
86
+ "pyright>=1.1.414",
87
+ ]
88
+
89
+ [[dependency-groups.dev]]
90
+ include-group = "test"
91
+
92
+ [[dependency-groups.dev]]
93
+ include-group = "lint"
94
+
95
+ [tool.uv.sources.artifactr-ai]
96
+ workspace = true
97
+
98
+ [tool.uv.sources.evalr]
99
+ workspace = true
100
+
101
+ [tool.uv.sources.lattice-core-ai]
102
+ workspace = true
103
+
104
+ [tool.uv.sources.reflexr]
105
+ workspace = true
106
+
107
+ [tool.uv.build-backend]
108
+ module-name = "relayr"
109
+
110
+ [tool.deptry.package_module_name_map]
111
+ artifactr-ai = "artifactr"
112
+ lattice-core-ai = "lattice_core"
113
+
114
+ [tool.pyright]
115
+ include = [
116
+ "src",
117
+ "tests",
118
+ "scripts",
119
+ ]
120
+ strict = [
121
+ "src",
122
+ "scripts",
123
+ ]
124
+ typeCheckingMode = "standard"
125
+ pythonVersion = "3.12"
126
+ venvPath = "../.."
127
+ venv = ".venv"
128
+
129
+ [tool.pytest.ini_options]
130
+ testpaths = ["tests"]
131
+ addopts = [
132
+ "--strict-markers",
133
+ "--strict-config",
134
+ "-ra",
135
+ ]
136
+ timeout = 60
137
+ timeout_method = "thread"
138
+ asyncio_mode = "auto"
139
+ asyncio_default_fixture_loop_scope = "function"
140
+ filterwarnings = ["error"]
141
+ markers = ["postgres: needs a PostgreSQL database (set RELAYR_TEST_POSTGRES_URL)"]
142
+
143
+ [tool.coverage.run]
144
+ branch = true
145
+ source = ["src/relayr"]
146
+ concurrency = [
147
+ "greenlet",
148
+ "thread",
149
+ ]
150
+
151
+ [tool.coverage.report]
152
+ fail_under = 100
153
+ show_missing = true
154
+ skip_covered = true
155
+ exclude_also = [
156
+ "if TYPE_CHECKING:",
157
+ "@overload",
158
+ 'assert_never\(',
159
+ "raise NotImplementedError",
160
+ '^\s*\.\.\.$',
161
+ 'class .*\(Protocol.*\):',
162
+ ]
163
+
164
+ [build-system]
165
+ requires = ["uv_build>=0.12,<0.13"]
166
+ build-backend = "uv_build"
@@ -0,0 +1,133 @@
1
+ [project]
2
+ name = "relayr-ai"
3
+ version = "0.1.0"
4
+ description = "The bridge between artifactr and reflexr: events from chats and artifacts become events for rules, and runs act back in the chat as proposals and notices."
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ license-files = ["LICENSE"]
8
+ authors = [{ name = "Alex Nodeland", email = "alex@ournature.studio" }]
9
+ requires-python = ">=3.12"
10
+ keywords = ["agents", "llm", "events", "rules", "collaboration", "artifacts", "pydantic-ai"]
11
+ classifiers = [
12
+ "Development Status :: 2 - Pre-Alpha",
13
+ "Framework :: Pydantic :: 2",
14
+ "Intended Audience :: Developers",
15
+ "Operating System :: OS Independent",
16
+ "Programming Language :: Python :: 3 :: Only",
17
+ "Programming Language :: Python :: 3.12",
18
+ "Programming Language :: Python :: 3.13",
19
+ "Programming Language :: Python :: 3.14",
20
+ "Topic :: Software Development :: Libraries :: Application Frameworks",
21
+ "Typing :: Typed",
22
+ ]
23
+ dependencies = [
24
+ "artifactr-ai>=0.1.0",
25
+ "reflexr>=0.1.0.dev0",
26
+ "pydantic>=2.13",
27
+ "pydantic-ai-slim>=2.51",
28
+ "opentelemetry-api>=1.45",
29
+ ]
30
+
31
+ [project.optional-dependencies]
32
+ sql = ["lattice-core-ai[sql]>=0.1.0", "sqlalchemy[asyncio]>=2.1", "alembic>=1.20"]
33
+ sqlite = ["relayr-ai[sql]", "aiosqlite>=0.22"]
34
+ postgres = ["relayr-ai[sql]", "asyncpg>=0.31"]
35
+ otel = ["lattice-core-ai>=0.1.0", "opentelemetry-sdk>=1.45"]
36
+ evals = ["evalr>=0.1.0.dev0"]
37
+ langfuse = [
38
+ "langfuse>=4.15,<5",
39
+ "artifactr-ai[langfuse]>=0.1.0",
40
+ "reflexr[langfuse]>=0.1.0.dev0",
41
+ # The libraries' [langfuse] extras need evalr; named here, it comes from the workspace too.
42
+ "evalr[langfuse]>=0.1.0.dev0",
43
+ ]
44
+
45
+ [project.urls]
46
+ Homepage = "https://lattice.alexnodeland.com/relayr/"
47
+ Documentation = "https://lattice.alexnodeland.com/relayr/"
48
+ Repository = "https://github.com/alexnodeland/lattice"
49
+ Issues = "https://github.com/alexnodeland/lattice/issues"
50
+ Changelog = "https://github.com/alexnodeland/lattice/blob/main/packages/relayr/CHANGELOG.md"
51
+
52
+ [dependency-groups]
53
+ test = [
54
+ "pytest>=9.1",
55
+ "pytest-asyncio>=1.4",
56
+ "pytest-cov>=7.1",
57
+ "pytest-timeout>=2.4",
58
+ "aiosqlite>=0.22",
59
+ "asyncpg>=0.31",
60
+ ]
61
+ lint = ["ruff>=0.16", "pyright>=1.1.414"]
62
+ dev = [
63
+ { include-group = "test" },
64
+ { include-group = "lint" },
65
+ ]
66
+
67
+ # In lattice, the libraries and lattice-core are the checkout's. A built wheel keeps the ranges
68
+ # in [project] alone.
69
+ [tool.uv.sources]
70
+ artifactr-ai = { workspace = true }
71
+ evalr = { workspace = true }
72
+ lattice-core-ai = { workspace = true }
73
+ reflexr = { workspace = true }
74
+
75
+ [build-system]
76
+ requires = ["uv_build>=0.12,<0.13"]
77
+ build-backend = "uv_build"
78
+
79
+ # Distributed as relayr-ai; imported as relayr.
80
+ [tool.uv.build-backend]
81
+ module-name = "relayr"
82
+
83
+ # ─── dependencies ─────────────────────────────────────────────────────────────
84
+
85
+ # deptry can't tell from the workspace's editable install which module a distribution named
86
+ # apart from its module provides.
87
+ [tool.deptry.package_module_name_map]
88
+ artifactr-ai = "artifactr"
89
+ lattice-core-ai = "lattice_core"
90
+
91
+ # ─── types ────────────────────────────────────────────────────────────────────
92
+
93
+ [tool.pyright]
94
+ include = ["src", "tests", "scripts"]
95
+ strict = ["src", "scripts"]
96
+ typeCheckingMode = "standard"
97
+ pythonVersion = "3.12"
98
+ venvPath = "../.."
99
+ venv = ".venv"
100
+
101
+ # ─── tests and coverage ───────────────────────────────────────────────────────
102
+
103
+ [tool.pytest.ini_options]
104
+ testpaths = ["tests"]
105
+ addopts = ["--strict-markers", "--strict-config", "-ra"]
106
+ # A test that runs for a minute is stuck: fail it, with every thread's stack.
107
+ timeout = 60
108
+ timeout_method = "thread"
109
+ asyncio_mode = "auto"
110
+ asyncio_default_fixture_loop_scope = "function"
111
+ filterwarnings = ["error"]
112
+ markers = [
113
+ "postgres: needs a PostgreSQL database (set RELAYR_TEST_POSTGRES_URL)",
114
+ ]
115
+
116
+ [tool.coverage.run]
117
+ branch = true
118
+ source = ["src/relayr"]
119
+ # SQLAlchemy runs run_sync callables in a greenlet, which coverage follows only when told.
120
+ concurrency = ["greenlet", "thread"]
121
+
122
+ [tool.coverage.report]
123
+ fail_under = 100
124
+ show_missing = true
125
+ skip_covered = true
126
+ exclude_also = [
127
+ "if TYPE_CHECKING:",
128
+ "@overload",
129
+ "assert_never\\(",
130
+ "raise NotImplementedError",
131
+ "^\\s*\\.\\.\\.$",
132
+ "class .*\\(Protocol.*\\):",
133
+ ]
@@ -0,0 +1,20 @@
1
+ """relayr: the bridge between artifactr and reflexr.
2
+
3
+ Events from chats and artifacts become events for rules, and runs act back in the chat as
4
+ proposals and notices. relayr uses only the two libraries' public APIs, and neither library
5
+ imports it or the other.
6
+
7
+ Its adapters are built: [`relayr.events`][relayr.events], the bridged types and the projections;
8
+ [`relayr.ledger`][relayr.ledger] and [`relayr.sql`][relayr.sql], its storage;
9
+ [`relayr.inbound`][relayr.inbound], the followers; [`relayr.outbound`][relayr.outbound], what
10
+ rules' runs do in artifactr; and [`relayr.telemetry`][relayr.telemetry], with
11
+ [`relayr.otel`][relayr.otel] and [`relayr.langfuse`][relayr.langfuse], their traces, tags and
12
+ counters. Its documentation, with its decisions and the
13
+ plan that builds the rest, is at <https://lattice.alexnodeland.com/relayr/>.
14
+ """
15
+
16
+ from importlib.metadata import version
17
+
18
+ __version__ = version("relayr-ai")
19
+
20
+ __all__ = ["__version__"]
@@ -0,0 +1,32 @@
1
+ """Evaluating the bridge's rules: proposal outcomes as feedback, and measures from the logs.
2
+
3
+ [`Outcomes`][relayr.evals.Outcomes] is the action of ``relayr:proposal-outcome``, which gives
4
+ each run whose proposal people resolved a [`ProposalOutcome`][relayr.evals.ProposalOutcome].
5
+ [`proposal_rates`][relayr.evals.proposal_rates] counts them by rule,
6
+ [`rule_conversion`][relayr.evals.rule_conversion] follows rules from chat from draft to
7
+ survival, and [`history`][relayr.evals.history] gives an artifact's versions to evalr's
8
+ measures, such as ``measure_rewrites``.
9
+ """
10
+
11
+ from relayr.evals.measures import ProposalRates, RuleConversion, proposal_rates, rule_conversion
12
+ from relayr.evals.outcomes import (
13
+ EVALUATOR,
14
+ PROPOSAL_OUTCOME,
15
+ RULES,
16
+ Outcomes,
17
+ ProposalOutcome,
18
+ )
19
+ from relayr.evals.revisions import history
20
+
21
+ __all__ = [
22
+ "EVALUATOR",
23
+ "PROPOSAL_OUTCOME",
24
+ "RULES",
25
+ "Outcomes",
26
+ "ProposalOutcome",
27
+ "ProposalRates",
28
+ "RuleConversion",
29
+ "history",
30
+ "proposal_rates",
31
+ "rule_conversion",
32
+ ]