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.
Files changed (57) hide show
  1. aif_normative_agent-0.3.1.dist-info/METADATA +114 -0
  2. aif_normative_agent-0.3.1.dist-info/RECORD +57 -0
  3. aif_normative_agent-0.3.1.dist-info/WHEEL +5 -0
  4. aif_normative_agent-0.3.1.dist-info/licenses/LICENSE +21 -0
  5. aif_normative_agent-0.3.1.dist-info/top_level.txt +3 -0
  6. normative_agent/__init__.py +81 -0
  7. normative_agent/agent.py +336 -0
  8. normative_agent/harness/__init__.py +52 -0
  9. normative_agent/harness/beliefs/__init__.py +35 -0
  10. normative_agent/harness/beliefs/builders.py +94 -0
  11. normative_agent/harness/beliefs/merge.py +35 -0
  12. normative_agent/harness/beliefs/render.py +11 -0
  13. normative_agent/harness/beliefs/task.py +49 -0
  14. normative_agent/harness/eda_client.py +150 -0
  15. normative_agent/harness/eda_session.py +171 -0
  16. normative_agent/harness/lfta_phases.py +55 -0
  17. normative_agent/harness/normbase_client.py +140 -0
  18. normative_agent/harness/projection_format.py +28 -0
  19. normative_agent/harness/snapshot_prompt.py +1312 -0
  20. normative_agent/harness/tool_executor.py +20 -0
  21. normative_agent/harness/urls.py +23 -0
  22. normative_agent/host/__init__.py +56 -0
  23. normative_agent/host/bind_context.py +30 -0
  24. normative_agent/host/candidate_actions.py +79 -0
  25. normative_agent/host/gather_policy.py +138 -0
  26. normative_agent/host/governance_hitl.py +90 -0
  27. normative_agent/host/hitl.py +73 -0
  28. normative_agent/host/loop.py +907 -0
  29. normative_agent/host/mcp_bridge.py +98 -0
  30. normative_agent/host/norm_coverage.py +99 -0
  31. normative_agent/host/policy.py +115 -0
  32. normative_agent/host/prompt_context.py +210 -0
  33. normative_agent/host/reflection.py +295 -0
  34. normative_agent/host/reflection_hitl.py +74 -0
  35. normative_agent/host/schemo_tools.py +855 -0
  36. normative_agent/host/tools.py +491 -0
  37. normative_agent/host/trigger_classifier.py +147 -0
  38. ontology/__init__.py +8 -0
  39. ontology/schemo/__init__.py +5 -0
  40. ontology/schemo/client/__init__.py +41 -0
  41. ontology/schemo/client/auth.py +34 -0
  42. ontology/schemo/client/http.py +902 -0
  43. ontology/schemo/contracts/__init__.py +54 -0
  44. ontology/schemo/contracts/belief_proposition.py +52 -0
  45. ontology/schemo/contracts/belief_render.py +208 -0
  46. ontology/schemo/contracts/query_facts.py +220 -0
  47. ontology/schemo/contracts/role_seeding_facts.py +81 -0
  48. ontology/schemo/contracts/task_facts.py +82 -0
  49. security/__init__.py +2 -0
  50. security/auth/__init__.py +20 -0
  51. security/auth/api_keys.py +67 -0
  52. security/auth/core.py +428 -0
  53. security/auth/mcp_tool_policy.json +137 -0
  54. security/auth/models.py +22 -0
  55. security/auth/org_access.py +119 -0
  56. security/auth/tool_policy.py +103 -0
  57. 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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (83.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -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,3 @@
1
+ normative_agent
2
+ ontology
3
+ security
@@ -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
+ ]
@@ -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
+ ]