aif-normative-agent 0.3.1__py3-none-any.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.
- aif_normative_agent-0.3.1.dist-info/METADATA +114 -0
- aif_normative_agent-0.3.1.dist-info/RECORD +57 -0
- aif_normative_agent-0.3.1.dist-info/WHEEL +5 -0
- aif_normative_agent-0.3.1.dist-info/licenses/LICENSE +21 -0
- aif_normative_agent-0.3.1.dist-info/top_level.txt +3 -0
- normative_agent/__init__.py +81 -0
- normative_agent/agent.py +336 -0
- normative_agent/harness/__init__.py +52 -0
- normative_agent/harness/beliefs/__init__.py +35 -0
- normative_agent/harness/beliefs/builders.py +94 -0
- normative_agent/harness/beliefs/merge.py +35 -0
- normative_agent/harness/beliefs/render.py +11 -0
- normative_agent/harness/beliefs/task.py +49 -0
- normative_agent/harness/eda_client.py +150 -0
- normative_agent/harness/eda_session.py +171 -0
- normative_agent/harness/lfta_phases.py +55 -0
- normative_agent/harness/normbase_client.py +140 -0
- normative_agent/harness/projection_format.py +28 -0
- normative_agent/harness/snapshot_prompt.py +1312 -0
- normative_agent/harness/tool_executor.py +20 -0
- normative_agent/harness/urls.py +23 -0
- normative_agent/host/__init__.py +56 -0
- normative_agent/host/bind_context.py +30 -0
- normative_agent/host/candidate_actions.py +79 -0
- normative_agent/host/gather_policy.py +138 -0
- normative_agent/host/governance_hitl.py +90 -0
- normative_agent/host/hitl.py +73 -0
- normative_agent/host/loop.py +907 -0
- normative_agent/host/mcp_bridge.py +98 -0
- normative_agent/host/norm_coverage.py +99 -0
- normative_agent/host/policy.py +115 -0
- normative_agent/host/prompt_context.py +210 -0
- normative_agent/host/reflection.py +295 -0
- normative_agent/host/reflection_hitl.py +74 -0
- normative_agent/host/schemo_tools.py +855 -0
- normative_agent/host/tools.py +491 -0
- normative_agent/host/trigger_classifier.py +147 -0
- ontology/__init__.py +8 -0
- ontology/schemo/__init__.py +5 -0
- ontology/schemo/client/__init__.py +41 -0
- ontology/schemo/client/auth.py +34 -0
- ontology/schemo/client/http.py +902 -0
- ontology/schemo/contracts/__init__.py +54 -0
- ontology/schemo/contracts/belief_proposition.py +52 -0
- ontology/schemo/contracts/belief_render.py +208 -0
- ontology/schemo/contracts/query_facts.py +220 -0
- ontology/schemo/contracts/role_seeding_facts.py +81 -0
- ontology/schemo/contracts/task_facts.py +82 -0
- security/__init__.py +2 -0
- security/auth/__init__.py +20 -0
- security/auth/api_keys.py +67 -0
- security/auth/core.py +428 -0
- security/auth/mcp_tool_policy.json +137 -0
- security/auth/models.py +22 -0
- security/auth/org_access.py +119 -0
- security/auth/tool_policy.py +103 -0
- security/auth/verifier.py +44 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: aif-normative-agent
|
|
3
|
+
Version: 0.3.1
|
|
4
|
+
Summary: Norm-Native Agent SDK — Harness + Host loop over EDA / Normbase / Schemo HTTP APIs
|
|
5
|
+
License: Proprietary License
|
|
6
|
+
|
|
7
|
+
Copyright (c) 2026 LangXAI. All rights reserved.
|
|
8
|
+
|
|
9
|
+
The Agent Information Field (AIF) software, documentation, and related
|
|
10
|
+
materials in this repository are proprietary and confidential.
|
|
11
|
+
|
|
12
|
+
No part of this software may be copied, modified, merged, published,
|
|
13
|
+
distributed, sublicensed, or sold without prior written permission from
|
|
14
|
+
LangXAI.
|
|
15
|
+
|
|
16
|
+
Access and use are permitted only for authorized users under applicable
|
|
17
|
+
service terms, license agreements, or other written authorization from
|
|
18
|
+
LangXAI.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
21
|
+
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
23
|
+
LANGXAI BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
24
|
+
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
25
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
26
|
+
|
|
27
|
+
Requires-Python: >=3.11
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
License-File: LICENSE
|
|
30
|
+
Requires-Dist: httpx>=0.26.0
|
|
31
|
+
Requires-Dist: aiohttp>=3.9.0
|
|
32
|
+
Provides-Extra: dev
|
|
33
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
34
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
|
|
35
|
+
Dynamic: license-file
|
|
36
|
+
|
|
37
|
+
# aif-normative-agent
|
|
38
|
+
|
|
39
|
+
PyPI distribution for the **Norm-Native Agent SDK** (`normative_agent`).
|
|
40
|
+
|
|
41
|
+
Thin HTTP client + Host tool loop over remote **EDA**, **Normbase**, and **Schemo** APIs. Does **not** ship Normbase, EDA server, Platform, MCP adapters, or Facilitator code.
|
|
42
|
+
|
|
43
|
+
Sibling package: [`aif-mcp`](../aif-mcp/README.md) (MCP tool surface over the same HTTP APIs).
|
|
44
|
+
|
|
45
|
+
## Install
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install aif-normative-agent
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Editable install from monorepo (full-stack development):
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pip install -e .
|
|
55
|
+
# at AIF repository root (aif-normbase)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
SDK-only wheel build (this directory):
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
cd packaging/normative-agent
|
|
62
|
+
python sync_sources.py
|
|
63
|
+
python -m build --wheel
|
|
64
|
+
python verify_wheel.py dist/aif_normative_agent-0.3.1-py3-none-any.whl
|
|
65
|
+
python verify_wheel_imports.py dist/aif_normative_agent-0.3.1-py3-none-any.whl
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Or from repo root:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
pwsh ./scripts/publish/publish_normative_agent.ps1
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Verify import
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
python -c "from normative_agent import NormativeAgent; print('ok')"
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Configuration
|
|
81
|
+
|
|
82
|
+
| Variable | Purpose |
|
|
83
|
+
|----------|---------|
|
|
84
|
+
| `AIF_AGENT_API_KEY` | Agent API key for `NormativeAgent.from_api_key` |
|
|
85
|
+
| `EDA_BASE_URL` | EDA HTTP API (default `http://localhost:8002`) |
|
|
86
|
+
| `NORMBASE_BASE_URL` | Normbase HTTP API (default `http://localhost:8001`) |
|
|
87
|
+
| `SCHEMO_QUERY_BASE_URL` | Schemo read API |
|
|
88
|
+
| `AIF_INTERNAL_SERVICE_TOKEN` | Optional internal Bearer for mixed deployments |
|
|
89
|
+
|
|
90
|
+
## Monorepo note
|
|
91
|
+
|
|
92
|
+
Source lives under `normative_agent/`, plus shared helpers `ontology/schemo/client`, `ontology/schemo/contracts`, and `security/auth` (same shared layer as `aif-mcp`).
|
|
93
|
+
|
|
94
|
+
Norm bind **build** logic stays in `eda_agent/norm_projection.py` (server). SDK formats EDA `bound_norm_projections` via `normative_agent.harness.projection_format`.
|
|
95
|
+
|
|
96
|
+
Developer guide: `normative_agent/docs/developer-guide.md` in the AIF monorepo (not shipped in the wheel).
|
|
97
|
+
|
|
98
|
+
Host app devkit: `docs/agent-app-devkit/` in the AIF repository.
|
|
99
|
+
|
|
100
|
+
Publish: `pwsh ./scripts/publish/publish_normative_agent.ps1`
|
|
101
|
+
|
|
102
|
+
## Package scope
|
|
103
|
+
|
|
104
|
+
Bundled:
|
|
105
|
+
|
|
106
|
+
- `normative_agent` (Harness + Host)
|
|
107
|
+
- `ontology.schemo.client` / `contracts`
|
|
108
|
+
- `security.auth`
|
|
109
|
+
|
|
110
|
+
Not bundled:
|
|
111
|
+
|
|
112
|
+
- `eda_agent`, `normbase`, `aif_platform`, `integrations`, `llm`, SDK docs under `normative_agent/docs/`
|
|
113
|
+
|
|
114
|
+
Optional LLM semantic governance uses monorepo `llm` or inject your own `ToolCallingModel` / `SemanticTriggerClassifier`.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
aif_normative_agent-0.3.1.dist-info/licenses/LICENSE,sha256=8XZjXNNp7M3ESlQanFGFqUabP-2uYRKS40j-aRewAeg,946
|
|
2
|
+
normative_agent/__init__.py,sha256=YSkrwzpRjiToD47jbeCYp8lYUm69Hm1G6TnEiBevyoE,1986
|
|
3
|
+
normative_agent/agent.py,sha256=xYc6a1k6inHD8UmDcOJjRvOj-QIDKo3JPVo7qejfep0,13207
|
|
4
|
+
normative_agent/harness/__init__.py,sha256=pKUQ96nVre2K8f_43qR_va0hQmNavq5Ue1S9hxKSJ5s,1650
|
|
5
|
+
normative_agent/harness/eda_client.py,sha256=CFcrQUbMYG5HMFdnTYW_YIYDpX_HaswF4H0pzUzP7rI,5237
|
|
6
|
+
normative_agent/harness/eda_session.py,sha256=8yQ6r4BlLe9X0vRt3DI74do22nMV9Anu9qePAzcZc7E,6634
|
|
7
|
+
normative_agent/harness/lfta_phases.py,sha256=opmJSQTYXxE3R3a1xV3F4PnO4LUxfCdfep7fLq5y_pw,1664
|
|
8
|
+
normative_agent/harness/normbase_client.py,sha256=XfOmthY-iMf3eMz9LTCjSszqXVNyBVSRoWnimjCSAvQ,4471
|
|
9
|
+
normative_agent/harness/projection_format.py,sha256=B8-pB8ZQ97JNFPfhurf7zQeSzmbz-_BP7kKoYvxADfA,1202
|
|
10
|
+
normative_agent/harness/snapshot_prompt.py,sha256=PcpBvHiZXG79c3LQOnXIg_vQrIkrauz5iCU0Jj6v1pA,55151
|
|
11
|
+
normative_agent/harness/tool_executor.py,sha256=NA4sYf2jLIQv1wx1rf9ZtTqGMN9ROOf8Qj38n0uydQY,734
|
|
12
|
+
normative_agent/harness/urls.py,sha256=QTy_3Ym6e3Pdy4NjeqkspRTrNrwJskpvBNUCsF1K1N4,985
|
|
13
|
+
normative_agent/harness/beliefs/__init__.py,sha256=nJU2jVjfH5i6jlZfva79ybkU--qOV7sz0I7e335qGgM,936
|
|
14
|
+
normative_agent/harness/beliefs/builders.py,sha256=CIVxvNoJg9RUakGYm2YUwlXHYuH6MRdRQikrbk5ZliI,2795
|
|
15
|
+
normative_agent/harness/beliefs/merge.py,sha256=sYQmDh5Sf18ej8bji5p8QlaudyAVz7GHwE7VoNskguQ,1173
|
|
16
|
+
normative_agent/harness/beliefs/render.py,sha256=ty-8pSPKoo24uT_s-_UvsPcX-2dmxc8wrDVzv3vP4eo,373
|
|
17
|
+
normative_agent/harness/beliefs/task.py,sha256=oyvU5LxUY1QKQM31Pp-s9VCmgDwEioM8tOYb6_8220I,1520
|
|
18
|
+
normative_agent/host/__init__.py,sha256=AmaU8lap23G_5eethWgx72dr1ROLT5sl7NLIc1sq9xQ,1491
|
|
19
|
+
normative_agent/host/bind_context.py,sha256=OEyMYfMRpsaJKZlXoYpptnIEwfiALhJ__HQL5EsXoF8,1179
|
|
20
|
+
normative_agent/host/candidate_actions.py,sha256=49fOz6-8XFfmWWIgimoPZEKo2wciIu5LIyaqTBSpn8I,2679
|
|
21
|
+
normative_agent/host/gather_policy.py,sha256=BHvTzeSGe8iyFot7zNu2VI-pEw5KIBlqoIoQD7j2wbg,4330
|
|
22
|
+
normative_agent/host/governance_hitl.py,sha256=UoDS3LNOMca_3_CLg7ro7Pv9XFbYL0LcYpz2FfzzMZw,3083
|
|
23
|
+
normative_agent/host/hitl.py,sha256=TAChj21WrQ_MjAqKV4DTcYrN_47YJADt9DYEQK6ztZ8,2753
|
|
24
|
+
normative_agent/host/loop.py,sha256=d7BmfQx8EOsecE_HR3dIwJDQ2OftWrh62DD4nZEY42s,36456
|
|
25
|
+
normative_agent/host/mcp_bridge.py,sha256=s3xi5qwEV1vHZazBfXuIo18eCC3o2rQdFQwafLQ191s,3603
|
|
26
|
+
normative_agent/host/norm_coverage.py,sha256=VFHRrLEGhIXJ0Up9z_4qxjJGqRIDL9GHqyKS4WCXqM0,3421
|
|
27
|
+
normative_agent/host/policy.py,sha256=HS9XY8Och6G6EPNJBICApiXxuvABdAMIelc1vxBMjSk,3787
|
|
28
|
+
normative_agent/host/prompt_context.py,sha256=tpKj_JvDMRPsFWBV_M2uZZyfzJzZXP-2ABHKbTVFeQw,6823
|
|
29
|
+
normative_agent/host/reflection.py,sha256=lf4T28qI1_2l6TgSUx4yi5m9xNUyTgpv5pgbRNNS_BU,10444
|
|
30
|
+
normative_agent/host/reflection_hitl.py,sha256=_YuJw-jn91U7Or4qAIMceMGmXdGegcw9OsgeIIr3_Q8,2178
|
|
31
|
+
normative_agent/host/schemo_tools.py,sha256=zZD1n58oZzDJBOjgY5mnb0km743YIoBscBAB0Kyimlk,30484
|
|
32
|
+
normative_agent/host/tools.py,sha256=NQ0H_zEq5wba2OxGoj8AV_iQghHXCIFN4_ynkHLorj8,18123
|
|
33
|
+
normative_agent/host/trigger_classifier.py,sha256=CvaYr-nd8PtcnYkjLwhlW0I0a9intF02xVzHyWpxLyI,5137
|
|
34
|
+
ontology/__init__.py,sha256=d76WqS5RxaUYieqWJ-HfM8GHgVBN1_ymZ9ZgZPZeKo8,276
|
|
35
|
+
ontology/schemo/__init__.py,sha256=o0PK86m90TGBgdxii4V2mJVbCpwzBJUPJVlX3FU_GIY,165
|
|
36
|
+
ontology/schemo/client/__init__.py,sha256=My1oqM1vMzSKfr9YH_1D09hHHef0ejDyfD_KcpIbojA,1104
|
|
37
|
+
ontology/schemo/client/auth.py,sha256=qo8cAeEWldSOffsaNCRg09_XOCpraLl5dVbd6VbA7r8,1101
|
|
38
|
+
ontology/schemo/client/http.py,sha256=gqtih3saIml3ypmTTHYq9dVUPTuXXfpPvHGnP9GlLXI,28146
|
|
39
|
+
ontology/schemo/contracts/__init__.py,sha256=tSo1tSA8KYwRGtrEfnF3Km3zrwEAhxabSTskMYQXVsM,1294
|
|
40
|
+
ontology/schemo/contracts/belief_proposition.py,sha256=--8Zo7NhRKrhdT8lWpwFAxTiJLTznAeAJxaqnS1akDo,1594
|
|
41
|
+
ontology/schemo/contracts/belief_render.py,sha256=VRqasrrvmRq-qXrc7tBH3kp9x-Mtej0FGJvyJ9GAOgA,6846
|
|
42
|
+
ontology/schemo/contracts/query_facts.py,sha256=3n5Woa67h6naaTGr7BbW19064KqnDw0W-byKyordY0Y,4758
|
|
43
|
+
ontology/schemo/contracts/role_seeding_facts.py,sha256=82cXgti7YOcfVeQ3Me155dIyCEEEFej_h1OVvVuxst8,1975
|
|
44
|
+
ontology/schemo/contracts/task_facts.py,sha256=sjedC2vziKsNztcdtzeEPFXHZ2ovhmCrmfqh5CkcUbo,2545
|
|
45
|
+
security/__init__.py,sha256=sHFe6p73O-VL_R-riB4RwwzJAdZ-xtfhMJAlumZQm_Q,61
|
|
46
|
+
security/auth/__init__.py,sha256=rZirZiwA7bdWGef2l30kBJ6CY_cSVMDqXfzMeP196Is,430
|
|
47
|
+
security/auth/api_keys.py,sha256=HFYbZ0ew-lAZXTbgYyhqcqLkjpioY6q7e06K_HqJi0Y,1665
|
|
48
|
+
security/auth/core.py,sha256=Ysz1tWTEZWIO2Vu5gxJUVKzQUJEDGXQIgsLWuKWvq3I,13860
|
|
49
|
+
security/auth/mcp_tool_policy.json,sha256=pYi5g6SsbKnFQWVCMRtWM8GEwtGj1zFcBA5yYE8vDZg,3359
|
|
50
|
+
security/auth/models.py,sha256=uD5eHUAEuCGUJ6kB7QOr3tjX2HECGrpOoPIPKsK9Qxw,566
|
|
51
|
+
security/auth/org_access.py,sha256=mdMWk_rUq5WcVKcqknEJ9QcVcCETNlw4wtaYHKVdBuI,3249
|
|
52
|
+
security/auth/tool_policy.py,sha256=S2lRsikMY73ERM9Pkkp7mIAXYc8wGNpOEuXHEMphWEQ,3216
|
|
53
|
+
security/auth/verifier.py,sha256=308_Q4mqEtA74EztfHmI3BtyAMZLe2ZisYNiOgLCoZw,1419
|
|
54
|
+
aif_normative_agent-0.3.1.dist-info/METADATA,sha256=TNIYyIK5uR0c1yDtGWdSNtyuaycf_yZGDjyCSpoOGd4,4069
|
|
55
|
+
aif_normative_agent-0.3.1.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
56
|
+
aif_normative_agent-0.3.1.dist-info/top_level.txt,sha256=Ra-LeoJVqrUelej7QxmiYH_I9YXeIjks5CdA54g3wd4,34
|
|
57
|
+
aif_normative_agent-0.3.1.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Proprietary License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 LangXAI. All rights reserved.
|
|
4
|
+
|
|
5
|
+
The Agent Information Field (AIF) software, documentation, and related
|
|
6
|
+
materials in this repository are proprietary and confidential.
|
|
7
|
+
|
|
8
|
+
No part of this software may be copied, modified, merged, published,
|
|
9
|
+
distributed, sublicensed, or sold without prior written permission from
|
|
10
|
+
LangXAI.
|
|
11
|
+
|
|
12
|
+
Access and use are permitted only for authorized users under applicable
|
|
13
|
+
service terms, license agreements, or other written authorization from
|
|
14
|
+
LangXAI.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
17
|
+
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
19
|
+
LANGXAI BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
20
|
+
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
21
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""Norm-Native Agent SDK (`normative_agent`) — Harness + Host for organizational LLM agents."""
|
|
2
|
+
|
|
3
|
+
from normative_agent.agent import NormativeAgent
|
|
4
|
+
from normative_agent.harness import (
|
|
5
|
+
DEFAULT_EDA_BASE_URL,
|
|
6
|
+
DEFAULT_NORMBASE_BASE_URL,
|
|
7
|
+
EdaAgentSession,
|
|
8
|
+
EdaHttpClient,
|
|
9
|
+
LftaPhase,
|
|
10
|
+
NormbaseHttpClient,
|
|
11
|
+
NoopToolExecutor,
|
|
12
|
+
ToolExecutor,
|
|
13
|
+
bind_context_for_phase,
|
|
14
|
+
cold_start_peer_beliefs,
|
|
15
|
+
cold_start_self_beliefs,
|
|
16
|
+
lens_for_phase,
|
|
17
|
+
SnapshotPromptOptions,
|
|
18
|
+
SnapshotPhase,
|
|
19
|
+
infer_snapshot_phase,
|
|
20
|
+
make_belief,
|
|
21
|
+
render_snapshot_prompt,
|
|
22
|
+
resolved_eda_base_url,
|
|
23
|
+
resolved_normbase_base_url,
|
|
24
|
+
task_beliefs_for_mandate,
|
|
25
|
+
)
|
|
26
|
+
from normative_agent.host import (
|
|
27
|
+
AgentLoopResult,
|
|
28
|
+
HitlPause,
|
|
29
|
+
MemoryLayers,
|
|
30
|
+
OrgAgentLoop,
|
|
31
|
+
ToolCallingModel,
|
|
32
|
+
ToolDefinition,
|
|
33
|
+
ToolRegistry,
|
|
34
|
+
TriggerDecision,
|
|
35
|
+
TriggerMode,
|
|
36
|
+
assemble_turn_messages,
|
|
37
|
+
classify_trigger,
|
|
38
|
+
eda_phases_for_mode,
|
|
39
|
+
register_eda_tools,
|
|
40
|
+
register_normbase_tools,
|
|
41
|
+
register_schemo_tools,
|
|
42
|
+
validate_candidate_actions_shape,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
__all__ = [
|
|
46
|
+
"DEFAULT_EDA_BASE_URL",
|
|
47
|
+
"DEFAULT_NORMBASE_BASE_URL",
|
|
48
|
+
"AgentLoopResult",
|
|
49
|
+
"EdaAgentSession",
|
|
50
|
+
"EdaHttpClient",
|
|
51
|
+
"HitlPause",
|
|
52
|
+
"LftaPhase",
|
|
53
|
+
"MemoryLayers",
|
|
54
|
+
"NormativeAgent",
|
|
55
|
+
"NormbaseHttpClient",
|
|
56
|
+
"NoopToolExecutor",
|
|
57
|
+
"OrgAgentLoop",
|
|
58
|
+
"ToolCallingModel",
|
|
59
|
+
"ToolDefinition",
|
|
60
|
+
"ToolExecutor",
|
|
61
|
+
"ToolRegistry",
|
|
62
|
+
"TriggerDecision",
|
|
63
|
+
"TriggerMode",
|
|
64
|
+
"bind_context_for_phase",
|
|
65
|
+
"classify_trigger",
|
|
66
|
+
"assemble_turn_messages",
|
|
67
|
+
"cold_start_peer_beliefs",
|
|
68
|
+
"cold_start_self_beliefs",
|
|
69
|
+
"eda_phases_for_mode",
|
|
70
|
+
"infer_snapshot_phase",
|
|
71
|
+
"lens_for_phase",
|
|
72
|
+
"make_belief",
|
|
73
|
+
"register_eda_tools",
|
|
74
|
+
"register_normbase_tools",
|
|
75
|
+
"register_schemo_tools",
|
|
76
|
+
"render_snapshot_prompt",
|
|
77
|
+
"resolved_eda_base_url",
|
|
78
|
+
"resolved_normbase_base_url",
|
|
79
|
+
"task_beliefs_for_mandate",
|
|
80
|
+
"validate_candidate_actions_shape",
|
|
81
|
+
]
|
normative_agent/agent.py
ADDED
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
"""Norm-Native Agent SDK (`normative_agent`): Harness + optional Host factory."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from typing import Any
|
|
5
|
+
from uuid import UUID
|
|
6
|
+
|
|
7
|
+
from security.auth.core import resolve_agent_api_key
|
|
8
|
+
|
|
9
|
+
from normative_agent.harness.eda_client import EdaHttpClient
|
|
10
|
+
from normative_agent.harness.eda_session import EdaAgentSession
|
|
11
|
+
from normative_agent.harness.urls import resolved_eda_base_url, resolved_normbase_base_url
|
|
12
|
+
from normative_agent.host.loop import OrgAgentLoop, ToolCallingModel
|
|
13
|
+
from normative_agent.host.tools import ToolRegistry, register_eda_tools, register_normbase_tools, register_schemo_tools
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class NormativeAgent:
|
|
17
|
+
"""
|
|
18
|
+
Facade for Norm-Native Agent SDK: **Harness** (EDA) + optional **Host** (agent loop).
|
|
19
|
+
|
|
20
|
+
**Harness** — :attr:`eda`, :meth:`eda_session`: read/write E/D/A, audit via EDA trace.
|
|
21
|
+
**Host** — :meth:`create_tool_registry`, :meth:`create_agent_loop`: Cursor-style tool loop.
|
|
22
|
+
|
|
23
|
+
Product name: Norm-Native Agent; Python package: ``normative_agent``. See ``docs/norm-native-agent-naming.md``.
|
|
24
|
+
|
|
25
|
+
Normbase remains the norm source of truth. Meta-phases are expressed in snapshot prompt
|
|
26
|
+
and ``OrgAgentLoop``; use ``eda_session()`` for deterministic EDA step sequences in tests.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
*,
|
|
32
|
+
agent_id: str,
|
|
33
|
+
eda_base_url: str | None = None,
|
|
34
|
+
normbase_base_url: str | None = None,
|
|
35
|
+
agent_api_key: str | None = None,
|
|
36
|
+
timeout_seconds: float = 60.0,
|
|
37
|
+
) -> None:
|
|
38
|
+
self._normbase_base_url = resolved_normbase_base_url(normbase_base_url)
|
|
39
|
+
self.eda = EdaHttpClient(
|
|
40
|
+
base_url=resolved_eda_base_url(eda_base_url),
|
|
41
|
+
agent_id=agent_id,
|
|
42
|
+
agent_api_key=agent_api_key,
|
|
43
|
+
timeout_seconds=timeout_seconds,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
@classmethod
|
|
47
|
+
async def from_api_key(
|
|
48
|
+
cls,
|
|
49
|
+
*,
|
|
50
|
+
agent_api_key: str,
|
|
51
|
+
eda_base_url: str | None = None,
|
|
52
|
+
normbase_base_url: str | None = None,
|
|
53
|
+
timeout_seconds: float = 60.0,
|
|
54
|
+
) -> NormativeAgent:
|
|
55
|
+
key = (agent_api_key or "").strip()
|
|
56
|
+
if not key:
|
|
57
|
+
raise ValueError("agent_api_key must be non-empty")
|
|
58
|
+
|
|
59
|
+
nb = resolved_normbase_base_url(normbase_base_url)
|
|
60
|
+
resolved = await resolve_agent_api_key(key, normbase_base_url=nb)
|
|
61
|
+
if resolved is None:
|
|
62
|
+
raise ValueError("Invalid agent API key or key is not an agent key")
|
|
63
|
+
raw_agent = (resolved.agent_id or "").strip()
|
|
64
|
+
if not raw_agent:
|
|
65
|
+
raise ValueError("API key is not bound to an agent")
|
|
66
|
+
try:
|
|
67
|
+
agent_uuid = str(UUID(raw_agent))
|
|
68
|
+
except ValueError as exc:
|
|
69
|
+
raise ValueError("API key returned invalid agent_id") from exc
|
|
70
|
+
|
|
71
|
+
return cls(
|
|
72
|
+
agent_id=agent_uuid,
|
|
73
|
+
eda_base_url=eda_base_url,
|
|
74
|
+
normbase_base_url=nb,
|
|
75
|
+
agent_api_key=key,
|
|
76
|
+
timeout_seconds=timeout_seconds,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
def eda_session(self) -> EdaAgentSession:
|
|
80
|
+
"""Harness: MCP-aligned EDA session."""
|
|
81
|
+
return EdaAgentSession(self.eda)
|
|
82
|
+
|
|
83
|
+
async def resolve_org_id(self, *, org_id: str | None = None) -> str:
|
|
84
|
+
"""
|
|
85
|
+
Resolve tenant ``org_id`` for Schemo org tools.
|
|
86
|
+
|
|
87
|
+
Uses explicit ``org_id`` when provided; otherwise reads ``org_id`` from the
|
|
88
|
+
bound EDA agent profile (``GET /agents/{id}``), which is available with the
|
|
89
|
+
same Agent API key as runtime calls.
|
|
90
|
+
"""
|
|
91
|
+
explicit = (org_id or "").strip()
|
|
92
|
+
if explicit:
|
|
93
|
+
try:
|
|
94
|
+
return str(UUID(explicit))
|
|
95
|
+
except ValueError as exc:
|
|
96
|
+
raise ValueError(f"Invalid org_id: {explicit!r}") from exc
|
|
97
|
+
|
|
98
|
+
profile = await self.eda.get_agent()
|
|
99
|
+
raw = profile.get("org_id")
|
|
100
|
+
text = str(raw).strip() if raw is not None else ""
|
|
101
|
+
if not text:
|
|
102
|
+
raise ValueError(
|
|
103
|
+
"org_id is required for Schemo tools; agent profile has no org_id. "
|
|
104
|
+
"Pass org_id= explicitly."
|
|
105
|
+
)
|
|
106
|
+
try:
|
|
107
|
+
return str(UUID(text))
|
|
108
|
+
except ValueError as exc:
|
|
109
|
+
raise ValueError(f"Agent profile returned invalid org_id: {raw!r}") from exc
|
|
110
|
+
|
|
111
|
+
def _schemo_credentials(self) -> "SchemoClientCredentials":
|
|
112
|
+
from ontology.schemo.client.auth import SchemoClientCredentials
|
|
113
|
+
|
|
114
|
+
return SchemoClientCredentials(
|
|
115
|
+
agent_api_key=self.eda.agent_api_key,
|
|
116
|
+
agent_id=self.eda.agent_id,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
def create_tool_registry(
|
|
120
|
+
self,
|
|
121
|
+
*,
|
|
122
|
+
include_eda: bool = True,
|
|
123
|
+
include_normbase: bool = True,
|
|
124
|
+
include_schemo: bool = True,
|
|
125
|
+
include_host_reflection: bool = True,
|
|
126
|
+
org_id: str | None = None,
|
|
127
|
+
schemo_base_url: str | None = None,
|
|
128
|
+
) -> ToolRegistry:
|
|
129
|
+
"""Host: tool registry with default ``eda_*``, Normbase, and Schemo org tools."""
|
|
130
|
+
if include_schemo and not (org_id or "").strip():
|
|
131
|
+
raise ValueError(
|
|
132
|
+
"org_id is required for Schemo tools. "
|
|
133
|
+
"Use await create_tool_registry_async(...) or pass org_id=."
|
|
134
|
+
)
|
|
135
|
+
if include_normbase and not self.eda.agent_api_key:
|
|
136
|
+
raise ValueError(
|
|
137
|
+
"agent_api_key is required for Normbase tools. "
|
|
138
|
+
"Use NormativeAgent.from_api_key(...)."
|
|
139
|
+
)
|
|
140
|
+
registry = ToolRegistry()
|
|
141
|
+
if include_eda:
|
|
142
|
+
register_eda_tools(registry, self.eda_session())
|
|
143
|
+
if include_normbase:
|
|
144
|
+
register_normbase_tools(
|
|
145
|
+
registry,
|
|
146
|
+
eda=self.eda,
|
|
147
|
+
normbase_base_url=self._normbase_base_url,
|
|
148
|
+
)
|
|
149
|
+
if include_schemo:
|
|
150
|
+
register_schemo_tools(
|
|
151
|
+
registry,
|
|
152
|
+
org_id=str(org_id or ""),
|
|
153
|
+
base_url=schemo_base_url,
|
|
154
|
+
credentials=self._schemo_credentials(),
|
|
155
|
+
)
|
|
156
|
+
if include_host_reflection:
|
|
157
|
+
from normative_agent.host.reflection import register_host_reflection_tools
|
|
158
|
+
|
|
159
|
+
register_host_reflection_tools(registry)
|
|
160
|
+
return registry
|
|
161
|
+
|
|
162
|
+
async def create_tool_registry_async(
|
|
163
|
+
self,
|
|
164
|
+
*,
|
|
165
|
+
include_eda: bool = True,
|
|
166
|
+
include_normbase: bool = True,
|
|
167
|
+
include_schemo: bool = True,
|
|
168
|
+
include_host_reflection: bool = True,
|
|
169
|
+
org_id: str | None = None,
|
|
170
|
+
schemo_base_url: str | None = None,
|
|
171
|
+
) -> ToolRegistry:
|
|
172
|
+
"""Host: like :meth:`create_tool_registry` but resolves ``org_id`` from EDA when omitted."""
|
|
173
|
+
resolved_org: str | None = org_id
|
|
174
|
+
if include_schemo:
|
|
175
|
+
resolved_org = await self.resolve_org_id(org_id=org_id)
|
|
176
|
+
return self.create_tool_registry(
|
|
177
|
+
include_eda=include_eda,
|
|
178
|
+
include_normbase=include_normbase,
|
|
179
|
+
include_schemo=include_schemo,
|
|
180
|
+
include_host_reflection=include_host_reflection,
|
|
181
|
+
org_id=resolved_org,
|
|
182
|
+
schemo_base_url=schemo_base_url,
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
async def build_system_prompt(
|
|
186
|
+
self,
|
|
187
|
+
*,
|
|
188
|
+
registry: ToolRegistry | None = None,
|
|
189
|
+
session: EdaAgentSession | None = None,
|
|
190
|
+
extra_instructions: str = "",
|
|
191
|
+
include_runtime_contract: bool = True,
|
|
192
|
+
include_info_routing: bool = True,
|
|
193
|
+
bind_completed: bool | None = None,
|
|
194
|
+
org_id: str | None = None,
|
|
195
|
+
schemo_base_url: str | None = None,
|
|
196
|
+
snapshot_prompt_options: "SnapshotPromptOptions | None" = None,
|
|
197
|
+
) -> str:
|
|
198
|
+
"""Homomorphic system prompt from EDA ``normative-snapshot`` (+ optional tool manifest)."""
|
|
199
|
+
from normative_agent.harness.snapshot_prompt import SnapshotPromptOptions
|
|
200
|
+
|
|
201
|
+
sess = session or self.eda_session()
|
|
202
|
+
tool_names: list[str] | None = None
|
|
203
|
+
if registry is not None:
|
|
204
|
+
tool_names = list(registry.tools.keys())
|
|
205
|
+
options = snapshot_prompt_options or SnapshotPromptOptions(
|
|
206
|
+
include_runtime_contract=include_runtime_contract,
|
|
207
|
+
include_info_routing=include_info_routing,
|
|
208
|
+
bind_completed=bind_completed,
|
|
209
|
+
)
|
|
210
|
+
resolved_org = (org_id or "").strip() or None
|
|
211
|
+
if not resolved_org and registry is not None and any(
|
|
212
|
+
k.startswith("schemo_") for k in registry.tools
|
|
213
|
+
):
|
|
214
|
+
try:
|
|
215
|
+
resolved_org = await self.resolve_org_id()
|
|
216
|
+
except ValueError:
|
|
217
|
+
resolved_org = None
|
|
218
|
+
return await sess.build_system_prompt(
|
|
219
|
+
tool_names=tool_names,
|
|
220
|
+
extra_instructions=extra_instructions,
|
|
221
|
+
options=options,
|
|
222
|
+
org_id=resolved_org,
|
|
223
|
+
schemo_base_url=schemo_base_url,
|
|
224
|
+
schemo_credentials=self._schemo_credentials(),
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
async def create_agent_loop(
|
|
228
|
+
self,
|
|
229
|
+
model: ToolCallingModel,
|
|
230
|
+
*,
|
|
231
|
+
registry: ToolRegistry | None = None,
|
|
232
|
+
max_steps: int = 24,
|
|
233
|
+
system_prompt: str = "",
|
|
234
|
+
auto_system_prompt: bool = False,
|
|
235
|
+
auto_refresh_prompt: bool = False,
|
|
236
|
+
initialize: dict | None = None,
|
|
237
|
+
extra_system_instructions: str = "",
|
|
238
|
+
require_execute_before_org: bool = True,
|
|
239
|
+
run_warmup: bool | None = None,
|
|
240
|
+
hitl_on_missing_role_norms: bool = True,
|
|
241
|
+
include_normbase: bool = True,
|
|
242
|
+
include_schemo: bool = True,
|
|
243
|
+
include_eda: bool = True,
|
|
244
|
+
org_id: str | None = None,
|
|
245
|
+
schemo_base_url: str | None = None,
|
|
246
|
+
trigger_classifier: Any | None = None,
|
|
247
|
+
snapshot_prompt_options: Any | None = None,
|
|
248
|
+
on_after_tool: Any | None = None,
|
|
249
|
+
require_reflection_gates: bool = True,
|
|
250
|
+
require_assess_before_report: bool = True,
|
|
251
|
+
require_dual_query_when_insufficient: bool = True,
|
|
252
|
+
perceive_on_bypass: bool = True,
|
|
253
|
+
runtime_timezone: str | None = None,
|
|
254
|
+
) -> OrgAgentLoop:
|
|
255
|
+
"""
|
|
256
|
+
Host: Cursor-style loop over ``registry`` and injected ``model``.
|
|
257
|
+
|
|
258
|
+
Default registry includes EDA, Normbase, and Schemo (``schemo_*``) tools.
|
|
259
|
+
Prefer :meth:`create_tool_registry_async` so ``org_id`` resolves from the EDA agent profile.
|
|
260
|
+
|
|
261
|
+
When ``auto_system_prompt=True``, fetches EDA ``normative-snapshot`` after optional
|
|
262
|
+
``initialize`` and renders the system prompt (``system_prompt`` wins if non-empty).
|
|
263
|
+
When ``run_warmup`` is omitted, it defaults to ``auto_system_prompt`` (perceive + bind at each ``run()``).
|
|
264
|
+
"""
|
|
265
|
+
reg = registry or await self.create_tool_registry_async(
|
|
266
|
+
include_eda=include_eda,
|
|
267
|
+
include_normbase=include_normbase,
|
|
268
|
+
include_schemo=include_schemo,
|
|
269
|
+
org_id=org_id,
|
|
270
|
+
schemo_base_url=schemo_base_url,
|
|
271
|
+
)
|
|
272
|
+
prompt_opts = snapshot_prompt_options
|
|
273
|
+
prompt = system_prompt.strip()
|
|
274
|
+
sess = self.eda_session()
|
|
275
|
+
resolved_org = (org_id or "").strip() or None
|
|
276
|
+
uses_schemo = include_schemo if registry is None else any(
|
|
277
|
+
k.startswith("schemo_") for k in reg.tools
|
|
278
|
+
)
|
|
279
|
+
if not resolved_org and uses_schemo:
|
|
280
|
+
try:
|
|
281
|
+
resolved_org = await self.resolve_org_id(org_id=org_id)
|
|
282
|
+
except ValueError:
|
|
283
|
+
resolved_org = None
|
|
284
|
+
|
|
285
|
+
prompt_builder = None
|
|
286
|
+
if auto_system_prompt and not prompt:
|
|
287
|
+
if initialize is not None:
|
|
288
|
+
await sess.initialize(initialize)
|
|
289
|
+
prompt = await self.build_system_prompt(
|
|
290
|
+
registry=reg,
|
|
291
|
+
session=sess,
|
|
292
|
+
org_id=resolved_org,
|
|
293
|
+
schemo_base_url=schemo_base_url,
|
|
294
|
+
snapshot_prompt_options=prompt_opts,
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
async def _prompt_builder() -> str:
|
|
298
|
+
return await self.build_system_prompt(
|
|
299
|
+
registry=reg,
|
|
300
|
+
session=sess,
|
|
301
|
+
org_id=resolved_org,
|
|
302
|
+
schemo_base_url=schemo_base_url,
|
|
303
|
+
snapshot_prompt_options=prompt_opts,
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
if auto_system_prompt:
|
|
307
|
+
prompt_builder = _prompt_builder
|
|
308
|
+
|
|
309
|
+
effective_warmup = run_warmup if run_warmup is not None else auto_system_prompt
|
|
310
|
+
from normative_agent.host.prompt_context import DEFAULT_RUNTIME_TIMEZONE
|
|
311
|
+
|
|
312
|
+
resolved_timezone = (
|
|
313
|
+
(runtime_timezone or DEFAULT_RUNTIME_TIMEZONE).strip() or DEFAULT_RUNTIME_TIMEZONE
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
return OrgAgentLoop(
|
|
317
|
+
reg,
|
|
318
|
+
model,
|
|
319
|
+
max_steps=max_steps,
|
|
320
|
+
system_prompt=prompt,
|
|
321
|
+
client_system_prompt=extra_system_instructions,
|
|
322
|
+
require_execute_before_org=require_execute_before_org,
|
|
323
|
+
prompt_builder=prompt_builder,
|
|
324
|
+
auto_refresh_prompt=auto_refresh_prompt,
|
|
325
|
+
eda_session=sess,
|
|
326
|
+
run_warmup=effective_warmup,
|
|
327
|
+
hitl_on_missing_role_norms=hitl_on_missing_role_norms,
|
|
328
|
+
trigger_classifier=trigger_classifier,
|
|
329
|
+
on_after_tool=on_after_tool,
|
|
330
|
+
require_reflection_gates=require_reflection_gates,
|
|
331
|
+
require_assess_before_report=require_assess_before_report,
|
|
332
|
+
require_dual_query_when_insufficient=require_dual_query_when_insufficient,
|
|
333
|
+
perceive_on_bypass=perceive_on_bypass,
|
|
334
|
+
runtime_timezone=resolved_timezone,
|
|
335
|
+
)
|
|
336
|
+
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Harness: EDA session, L-FTA lenses, beliefs, and org tool executor protocol (no agent loop)."""
|
|
2
|
+
|
|
3
|
+
from normative_agent.harness.eda_client import EdaHttpClient
|
|
4
|
+
from normative_agent.harness.eda_session import EdaAgentSession
|
|
5
|
+
from normative_agent.harness.snapshot_prompt import (
|
|
6
|
+
SnapshotPromptOptions,
|
|
7
|
+
SnapshotPhase,
|
|
8
|
+
infer_phase_from_prompt_text,
|
|
9
|
+
infer_snapshot_phase,
|
|
10
|
+
render_snapshot_prompt,
|
|
11
|
+
render_turn_directive,
|
|
12
|
+
)
|
|
13
|
+
from normative_agent.harness.lfta_phases import LftaPhase, bind_context_for_phase, lens_for_phase
|
|
14
|
+
from normative_agent.harness.tool_executor import NoopToolExecutor, ToolExecutor
|
|
15
|
+
from normative_agent.harness.urls import (
|
|
16
|
+
DEFAULT_EDA_BASE_URL,
|
|
17
|
+
DEFAULT_NORMBASE_BASE_URL,
|
|
18
|
+
resolved_eda_base_url,
|
|
19
|
+
resolved_normbase_base_url,
|
|
20
|
+
)
|
|
21
|
+
from normative_agent.harness.normbase_client import NormbaseHttpClient, active_field_ids_from_eda
|
|
22
|
+
from normative_agent.harness.beliefs import (
|
|
23
|
+
cold_start_peer_beliefs,
|
|
24
|
+
cold_start_self_beliefs,
|
|
25
|
+
make_belief,
|
|
26
|
+
task_beliefs_for_mandate,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
__all__ = [
|
|
30
|
+
"DEFAULT_EDA_BASE_URL",
|
|
31
|
+
"DEFAULT_NORMBASE_BASE_URL",
|
|
32
|
+
"EdaAgentSession",
|
|
33
|
+
"EdaHttpClient",
|
|
34
|
+
"LftaPhase",
|
|
35
|
+
"NormbaseHttpClient",
|
|
36
|
+
"SnapshotPromptOptions",
|
|
37
|
+
"SnapshotPhase",
|
|
38
|
+
"ToolExecutor",
|
|
39
|
+
"active_field_ids_from_eda",
|
|
40
|
+
"bind_context_for_phase",
|
|
41
|
+
"cold_start_peer_beliefs",
|
|
42
|
+
"cold_start_self_beliefs",
|
|
43
|
+
"infer_phase_from_prompt_text",
|
|
44
|
+
"infer_snapshot_phase",
|
|
45
|
+
"lens_for_phase",
|
|
46
|
+
"make_belief",
|
|
47
|
+
"render_snapshot_prompt",
|
|
48
|
+
"render_turn_directive",
|
|
49
|
+
"resolved_eda_base_url",
|
|
50
|
+
"resolved_normbase_base_url",
|
|
51
|
+
"task_beliefs_for_mandate",
|
|
52
|
+
]
|