contract4agents 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 (46) hide show
  1. contract4agents-0.1.0/LICENSE +21 -0
  2. contract4agents-0.1.0/PKG-INFO +132 -0
  3. contract4agents-0.1.0/README.md +76 -0
  4. contract4agents-0.1.0/pyproject.toml +141 -0
  5. contract4agents-0.1.0/src/contract4agents/__init__.py +7 -0
  6. contract4agents-0.1.0/src/contract4agents/__main__.py +6 -0
  7. contract4agents-0.1.0/src/contract4agents/adapters/__init__.py +0 -0
  8. contract4agents-0.1.0/src/contract4agents/adapters/openai.py +169 -0
  9. contract4agents-0.1.0/src/contract4agents/ast.py +135 -0
  10. contract4agents-0.1.0/src/contract4agents/cli.py +143 -0
  11. contract4agents-0.1.0/src/contract4agents/compiler/__init__.py +310 -0
  12. contract4agents-0.1.0/src/contract4agents/diagnostics.py +39 -0
  13. contract4agents-0.1.0/src/contract4agents/docscheck.py +69 -0
  14. contract4agents-0.1.0/src/contract4agents/evaluation.py +84 -0
  15. contract4agents-0.1.0/src/contract4agents/expressions/__init__.py +31 -0
  16. contract4agents-0.1.0/src/contract4agents/expressions/_eval.py +152 -0
  17. contract4agents-0.1.0/src/contract4agents/expressions/_grammar.py +212 -0
  18. contract4agents-0.1.0/src/contract4agents/expressions/_model.py +29 -0
  19. contract4agents-0.1.0/src/contract4agents/expressions/_refs.py +18 -0
  20. contract4agents-0.1.0/src/contract4agents/expressions/_trace_ops.py +56 -0
  21. contract4agents-0.1.0/src/contract4agents/fixtures/__init__.py +123 -0
  22. contract4agents-0.1.0/src/contract4agents/fixtures/_artifacts.py +65 -0
  23. contract4agents-0.1.0/src/contract4agents/fixtures/_execution.py +83 -0
  24. contract4agents-0.1.0/src/contract4agents/fixtures/_models.py +75 -0
  25. contract4agents-0.1.0/src/contract4agents/fixtures/_reports.py +72 -0
  26. contract4agents-0.1.0/src/contract4agents/monitor.py +46 -0
  27. contract4agents-0.1.0/src/contract4agents/parser/__init__.py +57 -0
  28. contract4agents-0.1.0/src/contract4agents/parser/_grammar.py +91 -0
  29. contract4agents-0.1.0/src/contract4agents/parser/_transformer.py +323 -0
  30. contract4agents-0.1.0/src/contract4agents/parser/_values.py +61 -0
  31. contract4agents-0.1.0/src/contract4agents/runtime/__init__.py +53 -0
  32. contract4agents-0.1.0/src/contract4agents/runtime/_datasources.py +301 -0
  33. contract4agents-0.1.0/src/contract4agents/runtime/_errors.py +64 -0
  34. contract4agents-0.1.0/src/contract4agents/runtime/_tools.py +59 -0
  35. contract4agents-0.1.0/src/contract4agents/runtime/_trace.py +41 -0
  36. contract4agents-0.1.0/src/contract4agents/runtime/_trace_io.py +49 -0
  37. contract4agents-0.1.0/src/contract4agents/runtime/_utils.py +23 -0
  38. contract4agents-0.1.0/src/contract4agents/schema.py +86 -0
  39. contract4agents-0.1.0/src/contract4agents/semantics.py +335 -0
  40. contract4agents-0.1.0/src/contract4agents/visualization/__init__.py +30 -0
  41. contract4agents-0.1.0/src/contract4agents/visualization/_artifacts.py +23 -0
  42. contract4agents-0.1.0/src/contract4agents/visualization/_graph.py +185 -0
  43. contract4agents-0.1.0/src/contract4agents/visualization/_html.py +508 -0
  44. contract4agents-0.1.0/src/contract4agents/visualization/_mermaid.py +82 -0
  45. contract4agents-0.1.0/src/contract4agents/visualization/_types.py +60 -0
  46. contract4agents-0.1.0/src/contract4agents/visualization/_utils.py +39 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026, B.T. Franklin
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,132 @@
1
+ Metadata-Version: 2.1
2
+ Name: contract4agents
3
+ Version: 0.1.0
4
+ Summary: Typed declarative contracts for AI agent systems.
5
+ Keywords: agents,contracts,evals,llm,openai,tooling
6
+ Author-Email: "B.T. Franklin" <brandon.franklin@gmail.com>
7
+ License: MIT License
8
+
9
+ Copyright (c) 2026, B.T. Franklin
10
+
11
+ Permission is hereby granted, free of charge, to any person obtaining a copy
12
+ of this software and associated documentation files (the "Software"), to deal
13
+ in the Software without restriction, including without limitation the rights
14
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15
+ copies of the Software, and to permit persons to whom the Software is
16
+ furnished to do so, subject to the following conditions:
17
+
18
+ The above copyright notice and this permission notice shall be included in all
19
+ copies or substantial portions of the Software.
20
+
21
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27
+ SOFTWARE.
28
+
29
+ Classifier: Programming Language :: Python :: 3
30
+ Classifier: Programming Language :: Python :: 3 :: Only
31
+ Classifier: Programming Language :: Python :: 3.11
32
+ Classifier: Programming Language :: Python :: 3.12
33
+ Classifier: Programming Language :: Python :: 3.13
34
+ Classifier: Programming Language :: Python :: 3.14
35
+ Classifier: Typing :: Typed
36
+ Classifier: License :: OSI Approved :: MIT License
37
+ Classifier: Operating System :: OS Independent
38
+ Classifier: Intended Audience :: Developers
39
+ Classifier: Environment :: Console
40
+ Classifier: Topic :: File Formats
41
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
42
+ Classifier: Topic :: Software Development :: Code Generators
43
+ Project-URL: Homepage, https://github.com/btfranklin/contract4agents
44
+ Project-URL: Issues, https://github.com/btfranklin/contract4agents/issues
45
+ Project-URL: Changelog, https://github.com/btfranklin/contract4agents/releases
46
+ Project-URL: Repository, https://github.com/btfranklin/contract4agents.git
47
+ Requires-Python: >=3.11
48
+ Requires-Dist: click>=8.4.1
49
+ Requires-Dist: lark>=1.3.1
50
+ Requires-Dist: jsonschema>=4.26.0
51
+ Requires-Dist: promptdown>=1.1.6
52
+ Provides-Extra: openai
53
+ Requires-Dist: openai>=2.44.0; extra == "openai"
54
+ Requires-Dist: openai-agents>=0.17.7; extra == "openai"
55
+ Description-Content-Type: text/markdown
56
+
57
+ # Contract4Agents
58
+
59
+ ![Contract4Agents banner](https://raw.githubusercontent.com/btfranklin/contract4agents/main/.github/social%20preview/contract4agents_social_preview.jpg "Contract4Agents")
60
+
61
+ Contract4Agents is a typed declarative language and local toolchain for defining AI agents as inspectable contracts.
62
+
63
+ The source artifact is a `.contract` file. It describes an agent's callable interface, allowed capabilities, policies, guards, assertions, and output contract. The compiler turns that source into prompts, provider-neutral manifests, JSON Schemas, eval packs, monitor rules, and visualization artifacts.
64
+
65
+ Contract4Agents includes the compiler, CLI, local fixtures, monitor checks, runtime primitives, and provider adapters needed to use those contracts beside a host agent application.
66
+
67
+ Start here:
68
+
69
+ - `docs/tutorials/using-contract4agents-with-an-agent-app.md` explains how to
70
+ use Contract4Agents beside an existing agent SDK implementation.
71
+ - `VISION.md` explains the concept and why it exists.
72
+ - `examples/incident-command/README.md` is the most concrete first read: it
73
+ explains what users write, what the files mean, and what artifacts are
74
+ generated.
75
+ - `examples/multi-lens-research/README.md` shows a complex research team split
76
+ into focused expert lenses.
77
+ - `examples/market-research-brief/README.md` shows document-driven market
78
+ research against dated current-fact snapshots.
79
+ - `examples/README.md` explains the reusable pattern for future examples.
80
+ - `docs/index.md` is the documentation map.
81
+ - `docs/examples/incident-command-walkthrough.md` walks through the clone-only example.
82
+ - `docs/research/agent-sdk-pattern-survey.md` captures the cross-SDK patterns Contract4Agents should preserve.
83
+ - `docs/decisions/accepted-decisions.md` records choices that should not be reopened casually.
84
+ - `docs/implementation/roadmap.md` tracks the active backlog for VISION gaps that are not implemented yet.
85
+
86
+ ## What's Included
87
+
88
+ - `contract4agents` Python package
89
+ - `contract4agents` Click CLI
90
+ - Lark-backed parser
91
+ - semantic analyzer
92
+ - JSON Schema and provider-neutral manifest compiler
93
+ - local fake-tool and datasource runtime primitives
94
+ - eval and monitor runners
95
+ - static project visualization
96
+ - first OpenAI adapter and semantic judge adapter
97
+ - clone-only `Incident Command` example backed by SQLite fake data
98
+
99
+ ## Development Setup
100
+
101
+ ```bash
102
+ pdm install
103
+ pdm run contract4agents --help
104
+ ```
105
+
106
+ ## Useful Commands
107
+
108
+ ```bash
109
+ pdm run contract4agents check examples/incident-command
110
+ pdm run contract4agents compile examples/incident-command --out .contract/build
111
+ pdm run contract4agents visualize examples/incident-command --out .contract/build/visualization
112
+ pdm run contract4agents eval tests/fixtures/contract_projects/ops-desk-lab
113
+ pdm run docs-check
114
+ pdm run validate
115
+ ```
116
+
117
+ The `examples/incident-command` project is the public walkthrough fixture for check, compile, and visualization. The eval command currently uses the richer `tests/fixtures/contract_projects/ops-desk-lab` fixture because the reusable fixture runner expects a `fixture.json` project.
118
+
119
+ ## OpenAI Adapter Checks
120
+
121
+ The normal validation suite does not call external APIs. Live OpenAI checks are opt-in:
122
+
123
+ ```bash
124
+ CONTRACT4AGENTS_RUN_OPENAI_LIVE=1 pdm run test:openai-live
125
+ CONTRACT4AGENTS_RUN_OPENAI_AGENT_LIVE=1 pdm run test:openai-agent-live
126
+ ```
127
+
128
+ Those commands require `OPENAI_API_KEY` in the environment or in the ignored local `.env` file.
129
+
130
+ ## License
131
+
132
+ MIT. See `LICENSE`.
@@ -0,0 +1,76 @@
1
+ # Contract4Agents
2
+
3
+ ![Contract4Agents banner](https://raw.githubusercontent.com/btfranklin/contract4agents/main/.github/social%20preview/contract4agents_social_preview.jpg "Contract4Agents")
4
+
5
+ Contract4Agents is a typed declarative language and local toolchain for defining AI agents as inspectable contracts.
6
+
7
+ The source artifact is a `.contract` file. It describes an agent's callable interface, allowed capabilities, policies, guards, assertions, and output contract. The compiler turns that source into prompts, provider-neutral manifests, JSON Schemas, eval packs, monitor rules, and visualization artifacts.
8
+
9
+ Contract4Agents includes the compiler, CLI, local fixtures, monitor checks, runtime primitives, and provider adapters needed to use those contracts beside a host agent application.
10
+
11
+ Start here:
12
+
13
+ - `docs/tutorials/using-contract4agents-with-an-agent-app.md` explains how to
14
+ use Contract4Agents beside an existing agent SDK implementation.
15
+ - `VISION.md` explains the concept and why it exists.
16
+ - `examples/incident-command/README.md` is the most concrete first read: it
17
+ explains what users write, what the files mean, and what artifacts are
18
+ generated.
19
+ - `examples/multi-lens-research/README.md` shows a complex research team split
20
+ into focused expert lenses.
21
+ - `examples/market-research-brief/README.md` shows document-driven market
22
+ research against dated current-fact snapshots.
23
+ - `examples/README.md` explains the reusable pattern for future examples.
24
+ - `docs/index.md` is the documentation map.
25
+ - `docs/examples/incident-command-walkthrough.md` walks through the clone-only example.
26
+ - `docs/research/agent-sdk-pattern-survey.md` captures the cross-SDK patterns Contract4Agents should preserve.
27
+ - `docs/decisions/accepted-decisions.md` records choices that should not be reopened casually.
28
+ - `docs/implementation/roadmap.md` tracks the active backlog for VISION gaps that are not implemented yet.
29
+
30
+ ## What's Included
31
+
32
+ - `contract4agents` Python package
33
+ - `contract4agents` Click CLI
34
+ - Lark-backed parser
35
+ - semantic analyzer
36
+ - JSON Schema and provider-neutral manifest compiler
37
+ - local fake-tool and datasource runtime primitives
38
+ - eval and monitor runners
39
+ - static project visualization
40
+ - first OpenAI adapter and semantic judge adapter
41
+ - clone-only `Incident Command` example backed by SQLite fake data
42
+
43
+ ## Development Setup
44
+
45
+ ```bash
46
+ pdm install
47
+ pdm run contract4agents --help
48
+ ```
49
+
50
+ ## Useful Commands
51
+
52
+ ```bash
53
+ pdm run contract4agents check examples/incident-command
54
+ pdm run contract4agents compile examples/incident-command --out .contract/build
55
+ pdm run contract4agents visualize examples/incident-command --out .contract/build/visualization
56
+ pdm run contract4agents eval tests/fixtures/contract_projects/ops-desk-lab
57
+ pdm run docs-check
58
+ pdm run validate
59
+ ```
60
+
61
+ The `examples/incident-command` project is the public walkthrough fixture for check, compile, and visualization. The eval command currently uses the richer `tests/fixtures/contract_projects/ops-desk-lab` fixture because the reusable fixture runner expects a `fixture.json` project.
62
+
63
+ ## OpenAI Adapter Checks
64
+
65
+ The normal validation suite does not call external APIs. Live OpenAI checks are opt-in:
66
+
67
+ ```bash
68
+ CONTRACT4AGENTS_RUN_OPENAI_LIVE=1 pdm run test:openai-live
69
+ CONTRACT4AGENTS_RUN_OPENAI_AGENT_LIVE=1 pdm run test:openai-agent-live
70
+ ```
71
+
72
+ Those commands require `OPENAI_API_KEY` in the environment or in the ignored local `.env` file.
73
+
74
+ ## License
75
+
76
+ MIT. See `LICENSE`.
@@ -0,0 +1,141 @@
1
+ [build-system]
2
+ requires = [
3
+ "pdm-backend>=2.4.9",
4
+ ]
5
+ build-backend = "pdm.backend"
6
+
7
+ [project]
8
+ name = "contract4agents"
9
+ dynamic = []
10
+ description = "Typed declarative contracts for AI agent systems."
11
+ authors = [
12
+ { name = "B.T. Franklin", email = "brandon.franklin@gmail.com" },
13
+ ]
14
+ dependencies = [
15
+ "click>=8.4.1",
16
+ "lark>=1.3.1",
17
+ "jsonschema>=4.26.0",
18
+ "promptdown>=1.1.6",
19
+ ]
20
+ requires-python = ">=3.11"
21
+ readme = "README.md"
22
+ keywords = [
23
+ "agents",
24
+ "contracts",
25
+ "evals",
26
+ "llm",
27
+ "openai",
28
+ "tooling",
29
+ ]
30
+ classifiers = [
31
+ "Programming Language :: Python :: 3",
32
+ "Programming Language :: Python :: 3 :: Only",
33
+ "Programming Language :: Python :: 3.11",
34
+ "Programming Language :: Python :: 3.12",
35
+ "Programming Language :: Python :: 3.13",
36
+ "Programming Language :: Python :: 3.14",
37
+ "Typing :: Typed",
38
+ "License :: OSI Approved :: MIT License",
39
+ "Operating System :: OS Independent",
40
+ "Intended Audience :: Developers",
41
+ "Environment :: Console",
42
+ "Topic :: File Formats",
43
+ "Topic :: Software Development :: Libraries :: Python Modules",
44
+ "Topic :: Software Development :: Code Generators",
45
+ ]
46
+ version = "0.1.0"
47
+
48
+ [project.license]
49
+ file = "LICENSE"
50
+
51
+ [project.urls]
52
+ Homepage = "https://github.com/btfranklin/contract4agents"
53
+ Issues = "https://github.com/btfranklin/contract4agents/issues"
54
+ Changelog = "https://github.com/btfranklin/contract4agents/releases"
55
+ Repository = "https://github.com/btfranklin/contract4agents.git"
56
+
57
+ [project.scripts]
58
+ contract4agents = "contract4agents.cli:main"
59
+
60
+ [project.optional-dependencies]
61
+ openai = [
62
+ "openai>=2.44.0",
63
+ "openai-agents>=0.17.7",
64
+ ]
65
+
66
+ [tool.pdm]
67
+ distribution = true
68
+
69
+ [tool.pdm.version]
70
+ source = "scm"
71
+
72
+ [tool.pdm.build]
73
+ package-dir = "src"
74
+ excludes = [
75
+ "examples/**",
76
+ "tests/**",
77
+ ]
78
+
79
+ [tool.pdm.scripts]
80
+ test = "pytest"
81
+ "test:unit" = "pytest tests/unit --cov=contract4agents --cov-report=term-missing --cov-fail-under=90"
82
+ "test:integration" = "pytest tests/integration"
83
+ "test:openai-live" = "pytest tests/integration/test_openai_live.py"
84
+ "test:agent-fixture" = "pytest tests/integration/test_ops_desk_fixture.py"
85
+ "test:openai-agent-live" = "pytest tests/integration/test_openai_agent_live.py"
86
+ docs-check = "python -m contract4agents docs-check"
87
+ lint = "ruff check src tests examples"
88
+ typecheck = "mypy src"
89
+ contract4agents = "python -m contract4agents"
90
+
91
+ [tool.pdm.scripts.validate]
92
+ composite = [
93
+ "lint",
94
+ "typecheck",
95
+ "docs-check",
96
+ "test",
97
+ ]
98
+
99
+ [tool.pytest.ini_options]
100
+ testpaths = [
101
+ "tests",
102
+ ]
103
+ markers = [
104
+ "integration: tests that may require optional services or credentials",
105
+ ]
106
+
107
+ [tool.ruff]
108
+ line-length = 120
109
+ target-version = "py311"
110
+
111
+ [tool.ruff.lint]
112
+ select = [
113
+ "E",
114
+ "F",
115
+ "I",
116
+ "UP",
117
+ "B",
118
+ ]
119
+
120
+ [tool.mypy]
121
+ python_version = "3.11"
122
+ strict = true
123
+ warn_unused_ignores = true
124
+ warn_return_any = true
125
+ files = [
126
+ "src",
127
+ ]
128
+
129
+ [dependency-groups]
130
+ dev = [
131
+ "pytest>=9.1.1",
132
+ "pytest-cov>=7.1.0",
133
+ "ruff>=0.15.18",
134
+ "mypy>=2.1.0",
135
+ "pytest-asyncio>=1.4.0",
136
+ "types-jsonschema>=4.26.0.20260518",
137
+ ]
138
+ openai = [
139
+ "openai>=2.44.0",
140
+ "openai-agents>=0.17.7",
141
+ ]
@@ -0,0 +1,7 @@
1
+ """Contract4Agents Python package."""
2
+
3
+ from contract4agents.compiler import compile_project
4
+ from contract4agents.parser import parse_file, parse_project
5
+ from contract4agents.semantics import analyze_project
6
+
7
+ __all__ = ["analyze_project", "compile_project", "parse_file", "parse_project"]
@@ -0,0 +1,6 @@
1
+ """Module entry point for the Contract4Agents CLI."""
2
+
3
+ from contract4agents.cli import main
4
+
5
+ if __name__ == "__main__":
6
+ main()
@@ -0,0 +1,169 @@
1
+ """OpenAI Agents SDK adapter.
2
+
3
+ The adapter is intentionally thin: Contract4Agents compiles to provider-neutral
4
+ manifests first, and this module projects those manifests onto OpenAI's SDK.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import os
10
+ from dataclasses import dataclass
11
+ from typing import Any
12
+
13
+ from contract4agents.compiler import AgentManifest
14
+ from contract4agents.runtime import TraceRecorder
15
+
16
+ _RunHooksBase: type[Any]
17
+ try:
18
+ from agents import RunHooks as _ImportedRunHooks
19
+
20
+ _RunHooksBase = _ImportedRunHooks
21
+ except Exception: # noqa: BLE001 - optional adapter import boundary.
22
+ _RunHooksBase = object
23
+
24
+
25
+ @dataclass(frozen=True)
26
+ class OpenAIAdapterResult:
27
+ final_output: Any
28
+ last_agent: str | None
29
+ raw_result: Any
30
+
31
+
32
+ class OpenAIAdapterUnavailable(RuntimeError):
33
+ pass
34
+
35
+
36
+ def openai_tool_name(contract_name: str) -> str:
37
+ """Convert a Contract4Agents capability name into an OpenAI-safe tool name."""
38
+ return contract_name.replace(".", "__")
39
+
40
+
41
+ def contract_tool_name(openai_name: str) -> str:
42
+ """Convert a generated OpenAI tool name back into the Contract4Agents capability name."""
43
+ return openai_name.replace("__", ".")
44
+
45
+
46
+ class OpenAITraceHooks(_RunHooksBase): # type: ignore[misc]
47
+ """Minimal hook object that normalizes Agents SDK lifecycle events to Contract4Agents traces."""
48
+
49
+ def __init__(self, trace: TraceRecorder) -> None:
50
+ super().__init__()
51
+ self.trace = trace
52
+
53
+ async def on_agent_start(self, _context: Any, agent: Any) -> None:
54
+ self.trace.record("agent.started", agent=getattr(agent, "name", str(agent)))
55
+
56
+ async def on_agent_end(self, _context: Any, agent: Any, output: Any) -> None:
57
+ self.trace.record("agent.completed", agent=getattr(agent, "name", str(agent)), output=_serializable(output))
58
+
59
+ async def on_handoff(self, _context: Any, from_agent: Any, to_agent: Any) -> None:
60
+ self.trace.record(
61
+ "agent.handoff",
62
+ from_agent=getattr(from_agent, "name", str(from_agent)),
63
+ to_agent=getattr(to_agent, "name", str(to_agent)),
64
+ )
65
+
66
+ async def on_tool_start(self, _context: Any, agent: Any, tool: Any) -> None:
67
+ self.trace.record(
68
+ "tool.started",
69
+ agent=getattr(agent, "name", str(agent)),
70
+ tool=contract_tool_name(getattr(tool, "name", str(tool))),
71
+ )
72
+
73
+ async def on_tool_end(self, _context: Any, agent: Any, tool: Any, result: str) -> None:
74
+ self.trace.record(
75
+ "tool.completed",
76
+ agent=getattr(agent, "name", str(agent)),
77
+ tool=contract_tool_name(getattr(tool, "name", str(tool))),
78
+ result=_serializable(result),
79
+ )
80
+
81
+ async def on_llm_start(self, _context: Any, agent: Any, _system_prompt: str | None, input_items: list[Any]) -> None:
82
+ self.trace.record("llm.started", agent=getattr(agent, "name", str(agent)), input_count=len(input_items))
83
+
84
+ async def on_llm_end(self, _context: Any, agent: Any, _response: Any) -> None:
85
+ self.trace.record("llm.completed", agent=getattr(agent, "name", str(agent)))
86
+
87
+
88
+ def build_openai_agent(
89
+ manifest: AgentManifest,
90
+ instructions: str,
91
+ tools: list[Any] | None = None,
92
+ handoffs: list[Any] | None = None,
93
+ output_type: Any | None = None,
94
+ hooks: Any | None = None,
95
+ input_guardrails: list[Any] | None = None,
96
+ ) -> Any:
97
+ try:
98
+ from agents import Agent
99
+ except Exception as exc: # noqa: BLE001 - optional adapter import boundary.
100
+ raise OpenAIAdapterUnavailable("openai-agents is not installed") from exc
101
+ kwargs: dict[str, Any] = {
102
+ "name": manifest["agent"],
103
+ "instructions": instructions,
104
+ "model": manifest.get("model", "gpt-5.5"),
105
+ "tools": tools or [],
106
+ "handoffs": handoffs or [],
107
+ }
108
+ if output_type is not None:
109
+ kwargs["output_type"] = output_type
110
+ if hooks is not None:
111
+ kwargs["hooks"] = hooks
112
+ if input_guardrails is not None:
113
+ kwargs["input_guardrails"] = input_guardrails
114
+ return Agent(**kwargs)
115
+
116
+
117
+ async def run_openai_agent(
118
+ agent: Any,
119
+ user_input: str,
120
+ *,
121
+ context: Any | None = None,
122
+ max_turns: int | None = 10,
123
+ hooks: Any | None = None,
124
+ ) -> OpenAIAdapterResult:
125
+ try:
126
+ from agents import Runner
127
+ except Exception as exc: # noqa: BLE001 - optional adapter import boundary.
128
+ raise OpenAIAdapterUnavailable("openai-agents is not installed") from exc
129
+ result = await Runner.run(agent, user_input, context=context, max_turns=max_turns, hooks=hooks)
130
+ last_agent = getattr(getattr(result, "last_agent", None), "name", None)
131
+ return OpenAIAdapterResult(getattr(result, "final_output", None), last_agent, result)
132
+
133
+
134
+ class OpenAISemanticJudge:
135
+ def __init__(self, model: str = "gpt-5.5", api_key_env: str = "OPENAI_API_KEY") -> None:
136
+ self.model = model
137
+ self.api_key_env = api_key_env
138
+
139
+ async def judge(self, *, output: dict[str, Any], criterion: str) -> bool:
140
+ if not os.getenv(self.api_key_env):
141
+ raise OpenAIAdapterUnavailable(f"{self.api_key_env} is not set")
142
+ try:
143
+ from openai import AsyncOpenAI
144
+ except Exception as exc: # noqa: BLE001 - optional adapter import boundary.
145
+ raise OpenAIAdapterUnavailable("openai package is not installed") from exc
146
+ client = AsyncOpenAI()
147
+ response = await client.responses.create(
148
+ model=self.model,
149
+ input=[
150
+ {
151
+ "role": "system",
152
+ "content": "Return only PASS or FAIL. Evaluate whether the output satisfies the criterion.",
153
+ },
154
+ {
155
+ "role": "user",
156
+ "content": f"Criterion: {criterion}\nOutput: {output}",
157
+ },
158
+ ],
159
+ )
160
+ text = getattr(response, "output_text", "")
161
+ return str(text).strip().upper() == "PASS"
162
+
163
+
164
+ def _serializable(value: Any) -> Any:
165
+ if hasattr(value, "model_dump"):
166
+ return value.model_dump()
167
+ if isinstance(value, dict | list | str | int | float | bool) or value is None:
168
+ return value
169
+ return str(value)
@@ -0,0 +1,135 @@
1
+ """AST nodes for Contract4Agents source files."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from pathlib import Path
7
+ from typing import Any, Literal
8
+
9
+
10
+ @dataclass(frozen=True)
11
+ class SourceSpan:
12
+ path: Path
13
+ line: int
14
+ column: int = 1
15
+
16
+ def display(self) -> str:
17
+ return f"{self.path}:{self.line}:{self.column}"
18
+
19
+
20
+ @dataclass(frozen=True)
21
+ class FieldDef:
22
+ name: str
23
+ type_name: str
24
+ nullable: bool = False
25
+ default: str | None = None
26
+ span: SourceSpan | None = None
27
+
28
+ @property
29
+ def normalized_type(self) -> str:
30
+ return self.type_name.rstrip("?").strip()
31
+
32
+
33
+ @dataclass(frozen=True)
34
+ class TypeDef:
35
+ name: str
36
+ fields: list[FieldDef]
37
+ span: SourceSpan
38
+
39
+
40
+ Permission = Literal["available", "preapproved", "requires_approval", "denied", "sandboxed"]
41
+ UseKind = Literal["tool", "agent", "datasource"]
42
+
43
+
44
+ @dataclass(frozen=True)
45
+ class UseDecl:
46
+ kind: UseKind
47
+ name: str
48
+ source: str
49
+ permission: Permission = "available"
50
+ span: SourceSpan | None = None
51
+
52
+
53
+ @dataclass(frozen=True)
54
+ class DatasourceDef:
55
+ name: str
56
+ python: str
57
+ requires: list[str]
58
+ produces: str
59
+ render: str = "markdown"
60
+ cache: str = "run"
61
+ span: SourceSpan | None = None
62
+
63
+
64
+ @dataclass(frozen=True)
65
+ class AgentDef:
66
+ name: str
67
+ parameters: list[FieldDef]
68
+ return_type: str
69
+ uses: list[UseDecl]
70
+ attributes: dict[str, Any]
71
+ span: SourceSpan
72
+
73
+ def list_attr(self, key: str) -> list[str]:
74
+ value = self.attributes.get(key, [])
75
+ return value if isinstance(value, list) else []
76
+
77
+ def text_attr(self, key: str) -> str:
78
+ value = self.attributes.get(key, "")
79
+ return value if isinstance(value, str) else ""
80
+
81
+
82
+ @dataclass(frozen=True)
83
+ class EvalCase:
84
+ name: str
85
+ agent: str
86
+ givens: dict[str, str]
87
+ expects: list[str]
88
+ semantic_expects: list[str]
89
+ span: SourceSpan
90
+
91
+
92
+ @dataclass(frozen=True)
93
+ class MonitorDef:
94
+ name: str
95
+ agent: str
96
+ severity: str
97
+ condition: str
98
+ expectation: str
99
+ span: SourceSpan
100
+
101
+
102
+ @dataclass
103
+ class ContractModule:
104
+ path: Path
105
+ types: list[TypeDef] = field(default_factory=list)
106
+ datasources: list[DatasourceDef] = field(default_factory=list)
107
+ agents: list[AgentDef] = field(default_factory=list)
108
+ evals: list[EvalCase] = field(default_factory=list)
109
+ monitors: list[MonitorDef] = field(default_factory=list)
110
+
111
+
112
+ @dataclass
113
+ class ContractProject:
114
+ root: Path
115
+ modules: list[ContractModule]
116
+
117
+ @property
118
+ def types(self) -> dict[str, TypeDef]:
119
+ return {item.name: item for module in self.modules for item in module.types}
120
+
121
+ @property
122
+ def datasources(self) -> dict[str, DatasourceDef]:
123
+ return {item.name: item for module in self.modules for item in module.datasources}
124
+
125
+ @property
126
+ def agents(self) -> dict[str, AgentDef]:
127
+ return {item.name: item for module in self.modules for item in module.agents}
128
+
129
+ @property
130
+ def evals(self) -> list[EvalCase]:
131
+ return [item for module in self.modules for item in module.evals]
132
+
133
+ @property
134
+ def monitors(self) -> list[MonitorDef]:
135
+ return [item for module in self.modules for item in module.monitors]