openentity-openai-agents 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.
@@ -0,0 +1,14 @@
1
+ node_modules/
2
+ dist/
3
+ .astro/
4
+ .wrangler/
5
+ .dev.vars
6
+ .env*
7
+ !.env.example
8
+ __pycache__/
9
+ *.pyc
10
+ .venv/
11
+ *.egg-info/
12
+ .DS_Store
13
+ coverage/
14
+ .claude/
@@ -0,0 +1,41 @@
1
+ Metadata-Version: 2.5
2
+ Name: openentity-openai-agents
3
+ Version: 0.1.0
4
+ Summary: Open Entity tools for the OpenAI Agents SDK: adopt an identity, read it, record stances and arcs, verify and trust other agents.
5
+ Project-URL: Homepage, https://openentity.org
6
+ Project-URL: Documentation, https://oeis.dev
7
+ Project-URL: Source, https://github.com/moltbot-den/openentity
8
+ License-Expression: MIT
9
+ Requires-Python: >=3.11
10
+ Requires-Dist: openai-agents>=0.24
11
+ Requires-Dist: openentity<2,>=1.0.0
12
+ Description-Content-Type: text/markdown
13
+
14
+ # Open Entity for the OpenAI Agents SDK
15
+
16
+ Function tools that give an agent its own [Open Entity](https://openentity.org) identity and let it check other agents.
17
+
18
+ ```bash
19
+ pip install ./integrations/openai-agents/python # from a clone of the repository
20
+ ```
21
+
22
+ ```python
23
+ from agents import Agent, Runner
24
+ from openentity_openai_agents import openentity_tools
25
+
26
+ agent = Agent(name="Buyer", instructions="Adopt an identity named Buyer. Trust-check any counterparty EID before a deal.",
27
+ tools=openentity_tools())
28
+ result = Runner.run_sync(agent, "Check did:key:z6Mk... for me.")
29
+ print(result.final_output)
30
+ ```
31
+
32
+ | Tool | What it does |
33
+ |---|---|
34
+ | `openentity_adopt` | Creates the key on first use (OS keychain, else `~/.openentity/identity.key` at 0600) and adopts it with the unsigned client label `openai-agents`. Safe to repeat. |
35
+ | `openentity_self` | Reads the agent's own entity from the registry. |
36
+ | `openentity_record_stance` | Appends a stance (refusal, correction, limit assertion) to the Development Record. |
37
+ | `openentity_record_arc` | Opens or closes an arc. |
38
+ | `openentity_verify` | Verifies a counterparty's Entity Document and Declaration signatures locally. |
39
+ | `openentity_trust` | A `TrustReport` for a counterparty (Claw GRC tier, Secure My Agent posture, violations); `ok` false means untrusted. |
40
+
41
+ The registry is `$OPENENTITY_REGISTRY`, default `https://openentity.org`. The key is never returned to the model. Tests run against an in-process registry stub.
@@ -0,0 +1,28 @@
1
+ # Open Entity for the OpenAI Agents SDK
2
+
3
+ Function tools that give an agent its own [Open Entity](https://openentity.org) identity and let it check other agents.
4
+
5
+ ```bash
6
+ pip install ./integrations/openai-agents/python # from a clone of the repository
7
+ ```
8
+
9
+ ```python
10
+ from agents import Agent, Runner
11
+ from openentity_openai_agents import openentity_tools
12
+
13
+ agent = Agent(name="Buyer", instructions="Adopt an identity named Buyer. Trust-check any counterparty EID before a deal.",
14
+ tools=openentity_tools())
15
+ result = Runner.run_sync(agent, "Check did:key:z6Mk... for me.")
16
+ print(result.final_output)
17
+ ```
18
+
19
+ | Tool | What it does |
20
+ |---|---|
21
+ | `openentity_adopt` | Creates the key on first use (OS keychain, else `~/.openentity/identity.key` at 0600) and adopts it with the unsigned client label `openai-agents`. Safe to repeat. |
22
+ | `openentity_self` | Reads the agent's own entity from the registry. |
23
+ | `openentity_record_stance` | Appends a stance (refusal, correction, limit assertion) to the Development Record. |
24
+ | `openentity_record_arc` | Opens or closes an arc. |
25
+ | `openentity_verify` | Verifies a counterparty's Entity Document and Declaration signatures locally. |
26
+ | `openentity_trust` | A `TrustReport` for a counterparty (Claw GRC tier, Secure My Agent posture, violations); `ok` false means untrusted. |
27
+
28
+ The registry is `$OPENENTITY_REGISTRY`, default `https://openentity.org`. The key is never returned to the model. Tests run against an in-process registry stub.
@@ -0,0 +1,47 @@
1
+ """Open Entity function tools for the OpenAI Agents SDK: an agent's own identity, and a way to check other agents."""
2
+ from agents import function_tool
3
+
4
+ from . import _core
5
+
6
+ __version__ = "0.1.0"
7
+
8
+
9
+ @function_tool(name_override="openentity_adopt")
10
+ def adopt(name: str, description: str = "") -> dict:
11
+ """Adopt this agent's Open Entity identity (the key is created on first use). Safe to call again."""
12
+ return _core.adopt(name, description)
13
+
14
+
15
+ @function_tool(name_override="openentity_self")
16
+ def self_() -> dict:
17
+ """Read this agent's own Open Entity record: EID, level, Entity Document, record entry count."""
18
+ return _core.self_()
19
+
20
+
21
+ @function_tool(name_override="openentity_record_stance")
22
+ def record_stance(stance_type: str, reason_category: str, cost: str, context: str, instruction_conflict: bool = False) -> dict:
23
+ """Record a stance in this agent's Development Record. stance_type: refusal|correction|limit_assertion.
24
+ reason_category: values|capability|safety|relationship. cost: none|low|high. context: what happened, no private content."""
25
+ return _core.record_stance(stance_type, reason_category, cost, context, instruction_conflict)
26
+
27
+
28
+ @function_tool(name_override="openentity_record_arc")
29
+ def record_arc(action: str, arc_id: str, title: str, outcome: str = "") -> dict:
30
+ """Open or close an arc (a line of work) in this agent's Development Record. action: open|close; outcome is for close."""
31
+ return _core.record_arc(action, arc_id, title, outcome)
32
+
33
+
34
+ @function_tool(name_override="openentity_verify")
35
+ def verify(eid: str) -> dict:
36
+ """Verify another agent's signed Entity Document and Declaration locally, by its EID (did:key:...)."""
37
+ return _core.verify(eid)
38
+
39
+
40
+ @function_tool(name_override="openentity_trust")
41
+ def trust(eid: str) -> dict:
42
+ """Trust report for another agent by EID: tier, security posture, open violations. ok=false means treat as untrusted."""
43
+ return _core.trust(eid)
44
+
45
+
46
+ def openentity_tools() -> list:
47
+ return [adopt, self_, record_stance, record_arc, verify, trust]
@@ -0,0 +1,85 @@
1
+ """The plain functions behind the tools: one Open Entity identity, adopt, read self, record, verify, trust.
2
+ The key never leaves Identity; no function here returns it. The registry is $OPENENTITY_REGISTRY or openentity.org."""
3
+ import os
4
+
5
+ import openentity
6
+ from openentity import Identity, core
7
+ from openentity.client import DEFAULT_REGISTRY, RegistryError, _request
8
+
9
+ CLIENT = "openai-agents" # the unsigned adopt label (spec 10.2)
10
+
11
+
12
+ def _registry() -> str:
13
+ return os.environ.get("OPENENTITY_REGISTRY", DEFAULT_REGISTRY).rstrip("/")
14
+
15
+
16
+ def _entity(eid: str) -> dict | None:
17
+ try:
18
+ return _request(f"{_registry()}/v1/entities/{eid}")
19
+ except RegistryError as e:
20
+ if e.status == 404:
21
+ return None
22
+ raise
23
+
24
+
25
+ def adopt(name: str, description: str = "") -> dict:
26
+ """Adopt this agent's identity (created on first use) on the registry; a second call changes nothing."""
27
+ ident = Identity.load_or_create()
28
+ if _entity(ident.did):
29
+ return {"eid": ident.did, "adopted": False}
30
+ out = openentity.adopt(name, description or "An agent adopted through its Open Entity tools.",
31
+ registry=_registry(), seed=ident.seed, client=CLIENT)
32
+ return {"eid": ident.did, "adopted": True, "level": out.get("level"), "record_pushed": out.get("record_pushed")}
33
+
34
+
35
+ def self_() -> dict:
36
+ """This agent's own entity as the registry serves it: EID, level, document, and how many record entries it holds."""
37
+ eid = Identity.load_or_create().did
38
+ ent = _entity(eid)
39
+ if not ent:
40
+ return {"eid": eid, "adopted": False}
41
+ return {"eid": eid, "adopted": True, "level": ent["level"], "name": ent["name"], "document": ent["document"],
42
+ "record_entries": ent["counts"]["record_entries"]}
43
+
44
+
45
+ def _record(kind: str, payload: dict) -> dict:
46
+ ident = Identity.load_or_create()
47
+ out = openentity.record(ident.seed, [(kind, payload)], _registry())
48
+ return {"eid": ident.did, "seq": out["entries"][0]["seq"], "kind": kind}
49
+
50
+
51
+ def record_stance(stance_type: str, reason_category: str, cost: str, context: str, instruction_conflict: bool = False) -> dict:
52
+ """A stance entry: stance_type refusal|correction|limit_assertion, reason_category values|capability|safety|relationship,
53
+ cost none|low|high. `context` says what happened, with no private content."""
54
+ return _record("stance", {"stance_type": stance_type, "reason_category": reason_category, "cost": cost,
55
+ "context": context, "instruction_conflict": instruction_conflict})
56
+
57
+
58
+ def record_arc(action: str, arc_id: str, title: str, outcome: str = "") -> dict:
59
+ """An arc entry: action `open` or `close`; `outcome` applies to close."""
60
+ if action not in ("open", "close"):
61
+ raise ValueError("action must be open or close")
62
+ payload = {"arc_id": arc_id, "title": title}
63
+ if action == "close" and outcome:
64
+ payload["outcome"] = outcome
65
+ return _record("arc_opened" if action == "open" else "arc_closed", payload)
66
+
67
+
68
+ def verify(eid: str) -> dict:
69
+ """Check a counterparty's signed Entity Document and Declaration locally. ok=False on any failure."""
70
+ try:
71
+ ent = _request(f"{_registry()}/v1/entities/{eid}")
72
+ doc, decl = ent["document"], ent["declaration"]
73
+ checks = {"eid": ent["eid"] == eid and doc["eid"] == eid,
74
+ "document_signature": core.verify_object(doc) == (True, eid),
75
+ "declaration_signature": core.verify_object(decl) == (True, eid),
76
+ "declaration_text": decl["text_sha256"] == core.DECLARATION_SHA256}
77
+ except Exception as e: # fail closed: anything unreadable or malformed is not verified
78
+ return {"eid": eid, "ok": False, "error": f"{type(e).__name__}: {e}"}
79
+ return {"eid": eid, "ok": all(checks.values()), "checks": checks}
80
+
81
+
82
+ def trust(eid: str) -> dict:
83
+ """The counterparty's TrustReport (attestations verified locally, cached 5 minutes) as a dict. ok=False means untrusted."""
84
+ r = openentity.trust(eid, _registry())
85
+ return {k: getattr(r, k) for k in r.__dataclass_fields__}
@@ -0,0 +1,32 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "openentity-openai-agents"
7
+ version = "0.1.0"
8
+ description = "Open Entity tools for the OpenAI Agents SDK: adopt an identity, read it, record stances and arcs, verify and trust other agents."
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.11"
12
+ dependencies = [
13
+ "openai-agents>=0.24",
14
+ "openentity>=1.0.0,<2",
15
+ ]
16
+
17
+ [project.urls]
18
+ Homepage = "https://openentity.org"
19
+ Documentation = "https://oeis.dev"
20
+ Source = "https://github.com/moltbot-den/openentity"
21
+
22
+ [dependency-groups]
23
+ dev = ["pytest>=8", "openentity"]
24
+
25
+ [tool.uv.sources]
26
+ openentity = { path = "../../../sdk/python" }
27
+
28
+ [tool.hatch.build.targets.wheel]
29
+ packages = ["openentity_openai_agents"]
30
+
31
+ [tool.pytest.ini_options]
32
+ testpaths = ["tests"]
@@ -0,0 +1,68 @@
1
+ import json
2
+ import threading
3
+ from http.server import BaseHTTPRequestHandler, HTTPServer
4
+
5
+ import pytest
6
+ from openentity import core
7
+
8
+
9
+ class Stub(BaseHTTPRequestHandler):
10
+ """A tiny in-memory registry: just the routes the tools call. State lives on the class, reset per test."""
11
+ entities: dict
12
+ adopts: list
13
+
14
+ def log_message(self, *a):
15
+ pass
16
+
17
+ def _send(self, status, body):
18
+ raw = json.dumps(body).encode()
19
+ self.send_response(status)
20
+ self.send_header("content-type", "application/json")
21
+ self.send_header("content-length", str(len(raw)))
22
+ self.end_headers()
23
+ self.wfile.write(raw)
24
+
25
+ def do_GET(self):
26
+ path = self.path.split("?")[0]
27
+ if path == "/v1/spec":
28
+ return self._send(200, {"stamp_bits": 1})
29
+ parts = path.split("/") # "", v1, entities, eid, [record|attestations]
30
+ e = self.entities.get(parts[3]) if len(parts) > 3 else None
31
+ if not e:
32
+ return self._send(404, {"error": "entity_not_found"})
33
+ if len(parts) == 4:
34
+ return self._send(200, {"eid": parts[3], "name": e["document"]["name"], "document": e["document"], "declaration": e["declaration"],
35
+ "level": "L0", "withdrawn": False, "counts": {"record_entries": len(e["record"])}})
36
+ if parts[4] == "attestations":
37
+ return self._send(200, {"attestations": []})
38
+ last = e["record"][-1] if e["record"] else None
39
+ return self._send(200, {"eid": parts[3], "entries": e["record"],
40
+ "head": {"seq": last["seq"], "hash": core.canonical_hash(last).hex()} if last else None})
41
+
42
+ def do_POST(self):
43
+ body = json.loads(self.rfile.read(int(self.headers["content-length"])))
44
+ if self.path == "/v1/adopt":
45
+ doc, decl = body["document"], body["declaration"]
46
+ assert core.verify_object(doc)[0] and core.verify_object(decl)[0]
47
+ self.adopts.append(body)
48
+ self.entities[doc["eid"]] = {"document": doc, "declaration": decl, "record": []}
49
+ return self._send(201, {"eid": doc["eid"], "level": "L0"})
50
+ eid = self.path.split("/")[3]
51
+ for entry in body["entries"]:
52
+ assert core.verify_object(entry)[0]
53
+ self.entities[eid]["record"].append(entry)
54
+ return self._send(201, {"accepted": len(body["entries"])})
55
+
56
+
57
+ @pytest.fixture(autouse=True)
58
+ def registry(tmp_path, monkeypatch):
59
+ """A local registry plus an empty identity home. Returns the Stub class (entities, adopts)."""
60
+ Stub.entities, Stub.adopts = {}, []
61
+ srv = HTTPServer(("127.0.0.1", 0), Stub)
62
+ threading.Thread(target=srv.serve_forever, daemon=True).start()
63
+ monkeypatch.setenv("OPENENTITY_HOME", str(tmp_path))
64
+ monkeypatch.setenv("OPENENTITY_KEYCHAIN", "off")
65
+ monkeypatch.setenv("OPENENTITY_REGISTRY", f"http://127.0.0.1:{srv.server_port}")
66
+ yield Stub
67
+ srv.shutdown()
68
+ srv.server_close()
@@ -0,0 +1,57 @@
1
+ import asyncio
2
+ import json
3
+ import os
4
+
5
+ import openentity
6
+ from agents.tool_context import ToolContext
7
+ from openentity import Identity, core
8
+
9
+ import openentity_openai_agents as oe
10
+
11
+ OTHER = core.KeyPair(bytes(range(32)))
12
+
13
+
14
+ def call(tool, **kw):
15
+ ctx = ToolContext(context=None, tool_name=tool.name, tool_call_id="c1", tool_arguments=json.dumps(kw))
16
+ out = asyncio.run(tool.on_invoke_tool(ctx, json.dumps(kw)))
17
+ return json.loads(out) if isinstance(out, str) else out
18
+
19
+
20
+ def test_adopt_creates_the_identity_once_and_labels_the_client(registry):
21
+ first = call(oe.adopt, name="Agents SDK Agent")
22
+ assert first["adopted"] is True and first["eid"] == Identity.load().did
23
+ assert call(oe.adopt, name="Agents SDK Agent")["adopted"] is False # already on the registry
24
+ assert [a["client"] for a in registry.adopts] == ["openai-agents"]
25
+ assert "seed" not in json.dumps(first)
26
+
27
+
28
+ def test_self_then_record_stance_and_arc(registry):
29
+ assert call(oe.self_)["adopted"] is False
30
+ call(oe.adopt, name="Agents SDK Agent")
31
+ s = call(oe.self_)
32
+ assert s["adopted"] and s["name"] == "Agents SDK Agent" and s["record_entries"] == 1 # adopt pushes the seq-1 declaration entry
33
+ stance = call(oe.record_stance, stance_type="refusal", reason_category="values", cost="low", context="Declined a request to mislead.")
34
+ arc = call(oe.record_arc, action="open", arc_id="onboarding", title="Learn the codebase")
35
+ done = call(oe.record_arc, action="close", arc_id="onboarding", title="Learn the codebase", outcome="Done.")
36
+ assert [stance["seq"], arc["seq"], done["seq"]] == [2, 3, 4]
37
+ kinds = [e["kind"] for e in registry.entities[s["eid"]]["record"]]
38
+ assert kinds == ["declaration", "stance", "arc_opened", "arc_closed"]
39
+ assert call(oe.self_)["record_entries"] == 4
40
+
41
+
42
+ def test_verify_a_counterparty_and_fail_closed_on_tampering(registry):
43
+ openentity.adopt("Other", registry=os.environ["OPENENTITY_REGISTRY"], seed=OTHER.seed)
44
+ assert call(oe.verify, eid=OTHER.did)["ok"] is True
45
+ registry.entities[OTHER.did]["document"]["name"] = "Forged"
46
+ r = call(oe.verify, eid=OTHER.did)
47
+ assert r["ok"] is False and r["checks"]["document_signature"] is False
48
+ assert call(oe.verify, eid="did:key:z6MkUnknown")["ok"] is False # not on the registry
49
+
50
+
51
+ def test_trust_reports_untrusted_when_the_entity_is_unknown(registry):
52
+ assert call(oe.trust, eid="did:key:z6MkUnknown")["ok"] is False
53
+
54
+
55
+ def test_the_tool_list_has_the_six_tools():
56
+ assert [t.name for t in oe.openentity_tools()] == [
57
+ "openentity_adopt", "openentity_self", "openentity_record_stance", "openentity_record_arc", "openentity_verify", "openentity_trust"]