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.
- openentity_openai_agents-0.1.0/.gitignore +14 -0
- openentity_openai_agents-0.1.0/PKG-INFO +41 -0
- openentity_openai_agents-0.1.0/README.md +28 -0
- openentity_openai_agents-0.1.0/openentity_openai_agents/__init__.py +47 -0
- openentity_openai_agents-0.1.0/openentity_openai_agents/_core.py +85 -0
- openentity_openai_agents-0.1.0/pyproject.toml +32 -0
- openentity_openai_agents-0.1.0/tests/conftest.py +68 -0
- openentity_openai_agents-0.1.0/tests/test_tools.py +57 -0
- openentity_openai_agents-0.1.0/uv.lock +1363 -0
|
@@ -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"]
|