aclade 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.
- aclade-0.1.0/PKG-INFO +92 -0
- aclade-0.1.0/README.md +69 -0
- aclade-0.1.0/aclade/__init__.py +9 -0
- aclade-0.1.0/aclade/cli.py +132 -0
- aclade-0.1.0/aclade/client.py +221 -0
- aclade-0.1.0/aclade.egg-info/PKG-INFO +92 -0
- aclade-0.1.0/aclade.egg-info/SOURCES.txt +10 -0
- aclade-0.1.0/aclade.egg-info/dependency_links.txt +1 -0
- aclade-0.1.0/aclade.egg-info/entry_points.txt +2 -0
- aclade-0.1.0/aclade.egg-info/top_level.txt +1 -0
- aclade-0.1.0/pyproject.toml +36 -0
- aclade-0.1.0/setup.cfg +4 -0
aclade-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: aclade
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Python SDK for Aclade — autonomous AI experts that do real work. `import aclade`, delegate a task, read the report.
|
|
5
|
+
Author-email: Aclade <support@aclade.com>
|
|
6
|
+
License: Proprietary (UNLICENSED) — distributed exclusively for users of the Aclade platform
|
|
7
|
+
Project-URL: Homepage, https://aclade.com
|
|
8
|
+
Project-URL: npm (local connector), https://www.npmjs.com/package/aclade
|
|
9
|
+
Keywords: aclade,ai,agent,autonomous,experts,sdk,api
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: Other/Proprietary License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
21
|
+
Requires-Python: >=3.8
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# aclade
|
|
25
|
+
|
|
26
|
+
**The official Python SDK for [Aclade](https://aclade.com)** — autonomous AI experts that do real work.
|
|
27
|
+
|
|
28
|
+
`import aclade` and you can list your AI team, delegate tasks to individual experts, and read their reports — from any Python code, notebook, or pipeline.
|
|
29
|
+
|
|
30
|
+
## Install
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install aclade
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Python 3.8+, zero third-party dependencies (standard library only).
|
|
37
|
+
|
|
38
|
+
## Get a token
|
|
39
|
+
|
|
40
|
+
1. Sign in at [aclade.com](https://aclade.com)
|
|
41
|
+
2. **Settings → Local Agent → Get connection token** (the same token the local connector uses)
|
|
42
|
+
3. `export ACLADE_TOKEN=<TOKEN>`
|
|
43
|
+
|
|
44
|
+
Tokens are signed 12-hour sessions bound to your workspace. If you already connected a machine with the [npm connector](https://www.npmjs.com/package/aclade), the SDK picks the token up from `~/.aclade-agent.json` automatically — `import aclade` just works.
|
|
45
|
+
|
|
46
|
+
## Quickstart
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
import aclade
|
|
50
|
+
|
|
51
|
+
team = aclade.Aclade() # token from $ACLADE_TOKEN or ~/.aclade-agent.json
|
|
52
|
+
team = aclade.Aclade(token="<TOKEN>") # …or explicitly
|
|
53
|
+
|
|
54
|
+
# Who's on the roster?
|
|
55
|
+
for e in team.experts():
|
|
56
|
+
print(e["name"], "—", e["title"])
|
|
57
|
+
|
|
58
|
+
# Delegate a task and wait for the report
|
|
59
|
+
result = team.delegate("Priya", "Summarize our top 3 customer complaints and draft replies")
|
|
60
|
+
print(result["status"]) # completed | needs-input | needs-approval
|
|
61
|
+
print(result["run"]["report"])
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## The three outcomes
|
|
65
|
+
|
|
66
|
+
| `result["status"]` | Meaning | What to do |
|
|
67
|
+
|---|---|---|
|
|
68
|
+
| `completed` | The expert finished the work | Read `result["run"]["report"]` |
|
|
69
|
+
| `needs-input` | The expert asked clarifying questions | Re-call with `answers={"<question>": "<answer>"}` |
|
|
70
|
+
| `needs-approval` | A high-risk step awaits a human | Approve in the portal (email link goes to your admin) |
|
|
71
|
+
|
|
72
|
+
## CLI
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
aclade experts # roster
|
|
76
|
+
aclade run "Priya" "task" # delegate + print the report
|
|
77
|
+
aclade tasks # pending tasks by expert
|
|
78
|
+
aclade agent # local-agent fleet (connector installs)
|
|
79
|
+
aclade whoami # identity carried by the token
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Where does the AI run?
|
|
83
|
+
|
|
84
|
+
On aclade.com — not on your machine. This SDK is the official client: it holds your token, sends work, and returns results. The reasoning harness, tools, and per-second metering are Aclade platform services — the same way `import stripe` never contains Stripe's rails.
|
|
85
|
+
|
|
86
|
+
Optionally, your machine can also run the [zero-trust local connector](https://www.npmjs.com/package/aclade) (`npm i -g aclade`), which lets your experts act on your own filesystem, shell, and browser — outbound HTTPS polling only, no inbound ports.
|
|
87
|
+
|
|
88
|
+
## Security notes
|
|
89
|
+
|
|
90
|
+
- The token is a credential: anyone holding it can delegate work as your workspace (billed to your account). Keep it in an env var or secrets manager.
|
|
91
|
+
- This package ships no AI logic, model weights, or telemetry.
|
|
92
|
+
- License: proprietary (`UNLICENSED`), for users of the Aclade platform.
|
aclade-0.1.0/README.md
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# aclade
|
|
2
|
+
|
|
3
|
+
**The official Python SDK for [Aclade](https://aclade.com)** — autonomous AI experts that do real work.
|
|
4
|
+
|
|
5
|
+
`import aclade` and you can list your AI team, delegate tasks to individual experts, and read their reports — from any Python code, notebook, or pipeline.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install aclade
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Python 3.8+, zero third-party dependencies (standard library only).
|
|
14
|
+
|
|
15
|
+
## Get a token
|
|
16
|
+
|
|
17
|
+
1. Sign in at [aclade.com](https://aclade.com)
|
|
18
|
+
2. **Settings → Local Agent → Get connection token** (the same token the local connector uses)
|
|
19
|
+
3. `export ACLADE_TOKEN=<TOKEN>`
|
|
20
|
+
|
|
21
|
+
Tokens are signed 12-hour sessions bound to your workspace. If you already connected a machine with the [npm connector](https://www.npmjs.com/package/aclade), the SDK picks the token up from `~/.aclade-agent.json` automatically — `import aclade` just works.
|
|
22
|
+
|
|
23
|
+
## Quickstart
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
import aclade
|
|
27
|
+
|
|
28
|
+
team = aclade.Aclade() # token from $ACLADE_TOKEN or ~/.aclade-agent.json
|
|
29
|
+
team = aclade.Aclade(token="<TOKEN>") # …or explicitly
|
|
30
|
+
|
|
31
|
+
# Who's on the roster?
|
|
32
|
+
for e in team.experts():
|
|
33
|
+
print(e["name"], "—", e["title"])
|
|
34
|
+
|
|
35
|
+
# Delegate a task and wait for the report
|
|
36
|
+
result = team.delegate("Priya", "Summarize our top 3 customer complaints and draft replies")
|
|
37
|
+
print(result["status"]) # completed | needs-input | needs-approval
|
|
38
|
+
print(result["run"]["report"])
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## The three outcomes
|
|
42
|
+
|
|
43
|
+
| `result["status"]` | Meaning | What to do |
|
|
44
|
+
|---|---|---|
|
|
45
|
+
| `completed` | The expert finished the work | Read `result["run"]["report"]` |
|
|
46
|
+
| `needs-input` | The expert asked clarifying questions | Re-call with `answers={"<question>": "<answer>"}` |
|
|
47
|
+
| `needs-approval` | A high-risk step awaits a human | Approve in the portal (email link goes to your admin) |
|
|
48
|
+
|
|
49
|
+
## CLI
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
aclade experts # roster
|
|
53
|
+
aclade run "Priya" "task" # delegate + print the report
|
|
54
|
+
aclade tasks # pending tasks by expert
|
|
55
|
+
aclade agent # local-agent fleet (connector installs)
|
|
56
|
+
aclade whoami # identity carried by the token
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Where does the AI run?
|
|
60
|
+
|
|
61
|
+
On aclade.com — not on your machine. This SDK is the official client: it holds your token, sends work, and returns results. The reasoning harness, tools, and per-second metering are Aclade platform services — the same way `import stripe` never contains Stripe's rails.
|
|
62
|
+
|
|
63
|
+
Optionally, your machine can also run the [zero-trust local connector](https://www.npmjs.com/package/aclade) (`npm i -g aclade`), which lets your experts act on your own filesystem, shell, and browser — outbound HTTPS polling only, no inbound ports.
|
|
64
|
+
|
|
65
|
+
## Security notes
|
|
66
|
+
|
|
67
|
+
- The token is a credential: anyone holding it can delegate work as your workspace (billed to your account). Keep it in an env var or secrets manager.
|
|
68
|
+
- This package ships no AI logic, model weights, or telemetry.
|
|
69
|
+
- License: proprietary (`UNLICENSED`), for users of the Aclade platform.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""aclade — the official Python SDK for Aclade, the autonomous AI workforce.
|
|
2
|
+
|
|
3
|
+
The reasoning (the harness) runs on aclade.com infrastructure; this package
|
|
4
|
+
is how you drive it from Python, the way ``import stripe`` drives Stripe.
|
|
5
|
+
"""
|
|
6
|
+
from .client import Aclade, AcladeError, load_token
|
|
7
|
+
|
|
8
|
+
__version__ = "0.1.0"
|
|
9
|
+
__all__ = ["Aclade", "AcladeError", "load_token", "__version__"]
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""Command line: ``aclade experts | run | tasks | agent | whoami``."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import json
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
from . import __version__
|
|
9
|
+
from .client import Aclade, AcladeError
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _cmd_whoami(client, _args):
|
|
13
|
+
print(json.dumps(
|
|
14
|
+
{
|
|
15
|
+
"name": client.name,
|
|
16
|
+
"companyId": client.company_id,
|
|
17
|
+
"memberId": client.member_id,
|
|
18
|
+
"baseUrl": client.base_url,
|
|
19
|
+
},
|
|
20
|
+
indent=2,
|
|
21
|
+
))
|
|
22
|
+
return 0
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _cmd_experts(client, _args):
|
|
26
|
+
experts = client.experts()
|
|
27
|
+
if not experts:
|
|
28
|
+
print("No experts on this roster yet. Rent one in the portal: https://aclade.com")
|
|
29
|
+
return 0
|
|
30
|
+
for m in experts:
|
|
31
|
+
print("%-28s %-22s %s" % (m.get("name", "?"), m.get("role", "?"), m.get("title", "")))
|
|
32
|
+
return 0
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _cmd_run(client, args):
|
|
36
|
+
try:
|
|
37
|
+
answers = json.loads(args.answers) if args.answers else None
|
|
38
|
+
except ValueError:
|
|
39
|
+
print("aclade: --answers must be valid JSON, e.g. '{\"stack\": \"node\"}'", file=sys.stderr)
|
|
40
|
+
return 2
|
|
41
|
+
result = client.delegate(args.expert, args.task, answers=answers)
|
|
42
|
+
status = result.get("status")
|
|
43
|
+
if status == "completed":
|
|
44
|
+
print((result.get("run") or {}).get("report") or "(no report)")
|
|
45
|
+
return 0
|
|
46
|
+
if status == "needs-input":
|
|
47
|
+
print("The expert needs input before proceeding:")
|
|
48
|
+
for q in result.get("questions") or []:
|
|
49
|
+
print(" -", q if isinstance(q, str) else json.dumps(q))
|
|
50
|
+
print('Re-run with: aclade run "%s" "%s" --answers \'<json>\'' % (args.expert, args.task))
|
|
51
|
+
return 0
|
|
52
|
+
if status == "needs-approval":
|
|
53
|
+
print("A high-risk step requires approval — the admin email link is on its way.")
|
|
54
|
+
return 0
|
|
55
|
+
print(json.dumps(result, indent=2))
|
|
56
|
+
return 0
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _cmd_tasks(client, _args):
|
|
60
|
+
pending = (client.pending_tasks().get("pendingByExpert")) or {}
|
|
61
|
+
if not pending:
|
|
62
|
+
print("No pending tasks.")
|
|
63
|
+
for expert_id, n in pending.items():
|
|
64
|
+
print("%s: %d" % (expert_id, n))
|
|
65
|
+
return 0
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _cmd_agent(client, _args):
|
|
69
|
+
data = client.connector_status()
|
|
70
|
+
print("Installs: %s Online: %s" % (data.get("installs", 0), data.get("connected", 0)))
|
|
71
|
+
for a in data.get("agents") or []:
|
|
72
|
+
print(
|
|
73
|
+
" %s %s — %s v%s — %s tasks — last seen %s"
|
|
74
|
+
% (
|
|
75
|
+
"●" if a.get("online") else "○",
|
|
76
|
+
a.get("hostname"),
|
|
77
|
+
a.get("platform"),
|
|
78
|
+
a.get("version"),
|
|
79
|
+
a.get("tasksExecuted", 0),
|
|
80
|
+
a.get("lastSeenAt"),
|
|
81
|
+
)
|
|
82
|
+
)
|
|
83
|
+
return 0
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def main(argv=None):
|
|
87
|
+
parser = argparse.ArgumentParser(
|
|
88
|
+
prog="aclade",
|
|
89
|
+
description="Aclade — autonomous AI experts that do real work. "
|
|
90
|
+
"Delegate tasks from Python, and check your local-agent fleet.",
|
|
91
|
+
)
|
|
92
|
+
parser.add_argument("--version", action="version", version="aclade %s" % __version__)
|
|
93
|
+
parser.add_argument("--token", help="API token (default: $ACLADE_TOKEN, then ~/.aclade-agent.json)")
|
|
94
|
+
parser.add_argument("--host", default="https://aclade.com", help="Platform base URL")
|
|
95
|
+
sub = parser.add_subparsers(dest="command")
|
|
96
|
+
|
|
97
|
+
p = sub.add_parser("whoami", help="Show the identity carried by the token")
|
|
98
|
+
p.set_defaults(func=_cmd_whoami)
|
|
99
|
+
|
|
100
|
+
p = sub.add_parser("experts", help="List the AI experts on your roster")
|
|
101
|
+
p.set_defaults(func=_cmd_experts)
|
|
102
|
+
|
|
103
|
+
p = sub.add_parser("run", help="Delegate a task to an expert and wait for the report")
|
|
104
|
+
p.add_argument("expert", help="Expert id or name substring (see: aclade experts)")
|
|
105
|
+
p.add_argument("task", help="What the expert should do")
|
|
106
|
+
p.add_argument("--answers", help='JSON answers to clarifying questions, e.g. \'{"stack": "node"}\'')
|
|
107
|
+
p.set_defaults(func=_cmd_run)
|
|
108
|
+
|
|
109
|
+
p = sub.add_parser("tasks", help="Pending tasks by expert")
|
|
110
|
+
p.set_defaults(func=_cmd_tasks)
|
|
111
|
+
|
|
112
|
+
p = sub.add_parser("agent", help="Local-agent fleet for this workspace")
|
|
113
|
+
p.set_defaults(func=_cmd_agent)
|
|
114
|
+
|
|
115
|
+
args = parser.parse_args(argv)
|
|
116
|
+
if not getattr(args, "command", None):
|
|
117
|
+
parser.print_help()
|
|
118
|
+
return 0
|
|
119
|
+
try:
|
|
120
|
+
client = Aclade(token=args.token, base_url=args.host)
|
|
121
|
+
except AcladeError as e:
|
|
122
|
+
print("aclade: %s" % e, file=sys.stderr)
|
|
123
|
+
return 2
|
|
124
|
+
try:
|
|
125
|
+
return args.func(client, args)
|
|
126
|
+
except AcladeError as e:
|
|
127
|
+
print("aclade: %s" % e, file=sys.stderr)
|
|
128
|
+
return 1
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
if __name__ == "__main__":
|
|
132
|
+
sys.exit(main())
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
"""HTTP client for the Aclade platform. Standard library only."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import base64
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import urllib.error
|
|
8
|
+
import urllib.parse
|
|
9
|
+
import urllib.request
|
|
10
|
+
|
|
11
|
+
__all__ = ["Aclade", "AcladeError", "load_token"]
|
|
12
|
+
|
|
13
|
+
DEFAULT_BASE_URL = "https://aclade.com"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class AcladeError(RuntimeError):
|
|
17
|
+
"""The platform rejected a request (or no token was found)."""
|
|
18
|
+
|
|
19
|
+
def __init__(self, status, message):
|
|
20
|
+
super().__init__(("HTTP %s: %s" % (status, message)) if status else str(message))
|
|
21
|
+
self.status = status
|
|
22
|
+
self.message = message
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def load_token():
|
|
26
|
+
"""Resolve the API token: $ACLADE_TOKEN, then the local-agent config.
|
|
27
|
+
|
|
28
|
+
Machines that already run the npm connector (``aclade connect``) keep
|
|
29
|
+
their token in ``~/.aclade-agent.json`` — so ``import aclade`` just works
|
|
30
|
+
there without any extra setup.
|
|
31
|
+
"""
|
|
32
|
+
tok = os.environ.get("ACLADE_TOKEN", "").strip()
|
|
33
|
+
if tok:
|
|
34
|
+
return tok
|
|
35
|
+
candidates = (
|
|
36
|
+
os.path.join(os.path.expanduser("~"), ".aclade-agent.json"),
|
|
37
|
+
os.path.join(os.path.expanduser("~"), ".aclade", "token"),
|
|
38
|
+
)
|
|
39
|
+
for candidate in candidates:
|
|
40
|
+
try:
|
|
41
|
+
with open(candidate, "r", encoding="utf-8") as fh:
|
|
42
|
+
text = fh.read().strip()
|
|
43
|
+
except OSError:
|
|
44
|
+
continue
|
|
45
|
+
if not text:
|
|
46
|
+
continue
|
|
47
|
+
if text.startswith("{"):
|
|
48
|
+
try:
|
|
49
|
+
tok = (json.loads(text) or {}).get("token", "")
|
|
50
|
+
except ValueError:
|
|
51
|
+
tok = ""
|
|
52
|
+
if tok:
|
|
53
|
+
return tok
|
|
54
|
+
else:
|
|
55
|
+
return text
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class Aclade:
|
|
60
|
+
"""A client for the Aclade platform.
|
|
61
|
+
|
|
62
|
+
Example::
|
|
63
|
+
|
|
64
|
+
team = aclade.Aclade()
|
|
65
|
+
for e in team.experts():
|
|
66
|
+
print(e["name"], e["title"])
|
|
67
|
+
result = team.delegate("Priya", "Summarize our top 3 customer complaints")
|
|
68
|
+
print(result["run"]["report"])
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
def __init__(self, token=None, base_url=DEFAULT_BASE_URL, timeout=300):
|
|
72
|
+
self.token = token or load_token()
|
|
73
|
+
if not self.token:
|
|
74
|
+
raise AcladeError(
|
|
75
|
+
0,
|
|
76
|
+
"No Aclade token. Pass token=... or set ACLADE_TOKEN. "
|
|
77
|
+
"Issue one in the portal: https://aclade.com -> Settings -> Local Agent.",
|
|
78
|
+
)
|
|
79
|
+
self.base_url = base_url.rstrip("/")
|
|
80
|
+
self.timeout = timeout
|
|
81
|
+
self._claims = self._decode_claims(self.token)
|
|
82
|
+
|
|
83
|
+
@staticmethod
|
|
84
|
+
def _decode_claims(token):
|
|
85
|
+
"""Session tokens are signed base64url JSON; a client may read its own."""
|
|
86
|
+
try:
|
|
87
|
+
body = token.split(".", 1)[0]
|
|
88
|
+
pad = body + "=" * (-len(body) % 4)
|
|
89
|
+
raw = base64.urlsafe_b64decode(pad.encode("ascii"))
|
|
90
|
+
return json.loads(raw.decode("utf-8"))
|
|
91
|
+
except Exception:
|
|
92
|
+
return {}
|
|
93
|
+
|
|
94
|
+
@property
|
|
95
|
+
def company_id(self):
|
|
96
|
+
return self._claims.get("companyId")
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def member_id(self):
|
|
100
|
+
return self._claims.get("memberId")
|
|
101
|
+
|
|
102
|
+
@property
|
|
103
|
+
def name(self):
|
|
104
|
+
return self._claims.get("name")
|
|
105
|
+
|
|
106
|
+
def _require_workspace(self):
|
|
107
|
+
if not self.company_id:
|
|
108
|
+
raise AcladeError(
|
|
109
|
+
0,
|
|
110
|
+
"This token does not carry workspace claims. Use a token issued "
|
|
111
|
+
"by the portal (Settings -> Local Agent).",
|
|
112
|
+
)
|
|
113
|
+
return self.company_id
|
|
114
|
+
|
|
115
|
+
def _request(self, method, path, body=None, timeout=None):
|
|
116
|
+
url = self.base_url + path
|
|
117
|
+
data = json.dumps(body).encode("utf-8") if body is not None else None
|
|
118
|
+
req = urllib.request.Request(url, data=data, method=method)
|
|
119
|
+
req.add_header("Authorization", "Bearer " + self.token)
|
|
120
|
+
req.add_header("Accept", "application/json")
|
|
121
|
+
if data is not None:
|
|
122
|
+
req.add_header("Content-Type", "application/json")
|
|
123
|
+
try:
|
|
124
|
+
with urllib.request.urlopen(req, timeout=timeout or self.timeout) as resp:
|
|
125
|
+
payload = resp.read().decode("utf-8")
|
|
126
|
+
return json.loads(payload) if payload else {}
|
|
127
|
+
except urllib.error.HTTPError as e:
|
|
128
|
+
detail = ""
|
|
129
|
+
try:
|
|
130
|
+
raw = e.read().decode("utf-8")
|
|
131
|
+
try:
|
|
132
|
+
detail = (json.loads(raw) or {}).get("error") or raw[:300]
|
|
133
|
+
except ValueError:
|
|
134
|
+
detail = raw[:300]
|
|
135
|
+
except Exception:
|
|
136
|
+
detail = str(e)
|
|
137
|
+
raise AcladeError(e.code, detail) from None
|
|
138
|
+
except urllib.error.URLError as e:
|
|
139
|
+
raise AcladeError(0, "Could not reach %s: %s" % (self.base_url, e.reason)) from None
|
|
140
|
+
|
|
141
|
+
# ---- Roster ----------------------------------------------------------------
|
|
142
|
+
|
|
143
|
+
def workspace(self):
|
|
144
|
+
"""The durable company workspace: ``{"workspace": {company, members, ...}}``."""
|
|
145
|
+
cid = self._require_workspace()
|
|
146
|
+
return self._request("GET", "/api/workspace?companyId=" + urllib.parse.quote(cid))
|
|
147
|
+
|
|
148
|
+
def members(self):
|
|
149
|
+
"""Everyone on the roster (humans and experts)."""
|
|
150
|
+
return (self.workspace().get("workspace") or {}).get("members") or []
|
|
151
|
+
|
|
152
|
+
def experts(self):
|
|
153
|
+
"""The rented AI experts on your roster."""
|
|
154
|
+
return [m for m in self.members() if m.get("origin") == "rented"]
|
|
155
|
+
|
|
156
|
+
def expert(self, name_or_id):
|
|
157
|
+
"""Find one expert by id or (case-insensitive) name substring."""
|
|
158
|
+
for m in self.experts():
|
|
159
|
+
if m.get("id") == name_or_id:
|
|
160
|
+
return m
|
|
161
|
+
for m in self.experts():
|
|
162
|
+
if name_or_id.lower() in (m.get("name") or "").lower():
|
|
163
|
+
return m
|
|
164
|
+
raise AcladeError(0, "No expert matching %r — see: aclade experts" % name_or_id)
|
|
165
|
+
|
|
166
|
+
# ---- Delegation ------------------------------------------------------------
|
|
167
|
+
|
|
168
|
+
@staticmethod
|
|
169
|
+
def _expert_payload(m):
|
|
170
|
+
"""The expert context the platform's run endpoint expects."""
|
|
171
|
+
email = m.get("email") or ""
|
|
172
|
+
domain = email.split("@")[1] if "@" in email else "aclade.com"
|
|
173
|
+
expert = m.get("expert") or {}
|
|
174
|
+
integrations = expert.get("integrations") or []
|
|
175
|
+
return {
|
|
176
|
+
"id": m.get("id"),
|
|
177
|
+
"companyId": m.get("companyId"),
|
|
178
|
+
"name": m.get("name"),
|
|
179
|
+
"role": m.get("title") or m.get("role") or "Expert",
|
|
180
|
+
"roleId": m.get("role"),
|
|
181
|
+
"personaId": m.get("personaId"),
|
|
182
|
+
"companyDomain": domain,
|
|
183
|
+
"email": email,
|
|
184
|
+
"activities": expert.get("activities") or [],
|
|
185
|
+
"integrations": [i.get("type") for i in integrations if isinstance(i, dict)],
|
|
186
|
+
"replacingName": expert.get("replacingName"),
|
|
187
|
+
"replacingContext": expert.get("replacingContext"),
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
def delegate(self, expert, task, answers=None, attachments=None):
|
|
191
|
+
"""Hand a task to an expert and block until the platform returns.
|
|
192
|
+
|
|
193
|
+
``expert`` may be a member dict from :meth:`experts`/:meth:`expert` or
|
|
194
|
+
a name/id string. Returns the result dict:
|
|
195
|
+
|
|
196
|
+
* ``status == "completed"`` → ``result["run"]["report"]`` has the work
|
|
197
|
+
* ``status == "needs-input"`` → ``result["questions"]``; re-call with
|
|
198
|
+
``answers={...}`` to continue
|
|
199
|
+
* ``status == "needs-approval"`` → a high-risk step awaits a human; the
|
|
200
|
+
admin gets an approve/deny email (approve in the portal)
|
|
201
|
+
"""
|
|
202
|
+
if isinstance(expert, str):
|
|
203
|
+
expert = self.expert(expert)
|
|
204
|
+
body = {"expert": self._expert_payload(expert), "task": task}
|
|
205
|
+
if answers is not None:
|
|
206
|
+
body["answers"] = answers
|
|
207
|
+
if attachments is not None:
|
|
208
|
+
body["attachments"] = attachments
|
|
209
|
+
return self._request("POST", "/api/experts/run", body=body)
|
|
210
|
+
|
|
211
|
+
# ---- Observability -----------------------------------------------------------
|
|
212
|
+
|
|
213
|
+
def pending_tasks(self):
|
|
214
|
+
"""Tasks queued right now, by expert: ``{"pendingByExpert": {...}}``."""
|
|
215
|
+
self._require_workspace()
|
|
216
|
+
return self._request("GET", "/api/tenant/pending-tasks")
|
|
217
|
+
|
|
218
|
+
def connector_status(self):
|
|
219
|
+
"""Local-agent fleet for this workspace, as seen by aclade.com."""
|
|
220
|
+
self._require_workspace()
|
|
221
|
+
return self._request("GET", "/api/connector/token")
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: aclade
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Python SDK for Aclade — autonomous AI experts that do real work. `import aclade`, delegate a task, read the report.
|
|
5
|
+
Author-email: Aclade <support@aclade.com>
|
|
6
|
+
License: Proprietary (UNLICENSED) — distributed exclusively for users of the Aclade platform
|
|
7
|
+
Project-URL: Homepage, https://aclade.com
|
|
8
|
+
Project-URL: npm (local connector), https://www.npmjs.com/package/aclade
|
|
9
|
+
Keywords: aclade,ai,agent,autonomous,experts,sdk,api
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: Other/Proprietary License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
21
|
+
Requires-Python: >=3.8
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# aclade
|
|
25
|
+
|
|
26
|
+
**The official Python SDK for [Aclade](https://aclade.com)** — autonomous AI experts that do real work.
|
|
27
|
+
|
|
28
|
+
`import aclade` and you can list your AI team, delegate tasks to individual experts, and read their reports — from any Python code, notebook, or pipeline.
|
|
29
|
+
|
|
30
|
+
## Install
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install aclade
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Python 3.8+, zero third-party dependencies (standard library only).
|
|
37
|
+
|
|
38
|
+
## Get a token
|
|
39
|
+
|
|
40
|
+
1. Sign in at [aclade.com](https://aclade.com)
|
|
41
|
+
2. **Settings → Local Agent → Get connection token** (the same token the local connector uses)
|
|
42
|
+
3. `export ACLADE_TOKEN=<TOKEN>`
|
|
43
|
+
|
|
44
|
+
Tokens are signed 12-hour sessions bound to your workspace. If you already connected a machine with the [npm connector](https://www.npmjs.com/package/aclade), the SDK picks the token up from `~/.aclade-agent.json` automatically — `import aclade` just works.
|
|
45
|
+
|
|
46
|
+
## Quickstart
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
import aclade
|
|
50
|
+
|
|
51
|
+
team = aclade.Aclade() # token from $ACLADE_TOKEN or ~/.aclade-agent.json
|
|
52
|
+
team = aclade.Aclade(token="<TOKEN>") # …or explicitly
|
|
53
|
+
|
|
54
|
+
# Who's on the roster?
|
|
55
|
+
for e in team.experts():
|
|
56
|
+
print(e["name"], "—", e["title"])
|
|
57
|
+
|
|
58
|
+
# Delegate a task and wait for the report
|
|
59
|
+
result = team.delegate("Priya", "Summarize our top 3 customer complaints and draft replies")
|
|
60
|
+
print(result["status"]) # completed | needs-input | needs-approval
|
|
61
|
+
print(result["run"]["report"])
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## The three outcomes
|
|
65
|
+
|
|
66
|
+
| `result["status"]` | Meaning | What to do |
|
|
67
|
+
|---|---|---|
|
|
68
|
+
| `completed` | The expert finished the work | Read `result["run"]["report"]` |
|
|
69
|
+
| `needs-input` | The expert asked clarifying questions | Re-call with `answers={"<question>": "<answer>"}` |
|
|
70
|
+
| `needs-approval` | A high-risk step awaits a human | Approve in the portal (email link goes to your admin) |
|
|
71
|
+
|
|
72
|
+
## CLI
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
aclade experts # roster
|
|
76
|
+
aclade run "Priya" "task" # delegate + print the report
|
|
77
|
+
aclade tasks # pending tasks by expert
|
|
78
|
+
aclade agent # local-agent fleet (connector installs)
|
|
79
|
+
aclade whoami # identity carried by the token
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Where does the AI run?
|
|
83
|
+
|
|
84
|
+
On aclade.com — not on your machine. This SDK is the official client: it holds your token, sends work, and returns results. The reasoning harness, tools, and per-second metering are Aclade platform services — the same way `import stripe` never contains Stripe's rails.
|
|
85
|
+
|
|
86
|
+
Optionally, your machine can also run the [zero-trust local connector](https://www.npmjs.com/package/aclade) (`npm i -g aclade`), which lets your experts act on your own filesystem, shell, and browser — outbound HTTPS polling only, no inbound ports.
|
|
87
|
+
|
|
88
|
+
## Security notes
|
|
89
|
+
|
|
90
|
+
- The token is a credential: anyone holding it can delegate work as your workspace (billed to your account). Keep it in an env var or secrets manager.
|
|
91
|
+
- This package ships no AI logic, model weights, or telemetry.
|
|
92
|
+
- License: proprietary (`UNLICENSED`), for users of the Aclade platform.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
aclade
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "aclade"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Official Python SDK for Aclade — autonomous AI experts that do real work. `import aclade`, delegate a task, read the report."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
license = { text = "Proprietary (UNLICENSED) — distributed exclusively for users of the Aclade platform" }
|
|
12
|
+
authors = [{ name = "Aclade", email = "support@aclade.com" }]
|
|
13
|
+
keywords = ["aclade", "ai", "agent", "autonomous", "experts", "sdk", "api"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: Other/Proprietary License",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.8",
|
|
21
|
+
"Programming Language :: Python :: 3.9",
|
|
22
|
+
"Programming Language :: Python :: 3.10",
|
|
23
|
+
"Programming Language :: Python :: 3.11",
|
|
24
|
+
"Programming Language :: Python :: 3.12",
|
|
25
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[project.urls]
|
|
29
|
+
Homepage = "https://aclade.com"
|
|
30
|
+
"npm (local connector)" = "https://www.npmjs.com/package/aclade"
|
|
31
|
+
|
|
32
|
+
[project.scripts]
|
|
33
|
+
aclade = "aclade.cli:main"
|
|
34
|
+
|
|
35
|
+
[tool.setuptools]
|
|
36
|
+
packages = ["aclade"]
|
aclade-0.1.0/setup.cfg
ADDED