computer-agent-py 0.1.1__tar.gz → 0.2.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 (41) hide show
  1. {computer_agent_py-0.1.1 → computer_agent_py-0.2.0}/PKG-INFO +64 -2
  2. {computer_agent_py-0.1.1 → computer_agent_py-0.2.0}/README.md +54 -1
  3. {computer_agent_py-0.1.1 → computer_agent_py-0.2.0}/pyproject.toml +25 -2
  4. computer_agent_py-0.2.0/src/computeragent/__init__.py +163 -0
  5. {computer_agent_py-0.1.1 → computer_agent_py-0.2.0}/src/computeragent/_proxy/client.py +9 -1
  6. {computer_agent_py-0.1.1 → computer_agent_py-0.2.0}/src/computeragent/_proxy/query.py +33 -7
  7. computer_agent_py-0.2.0/src/computeragent/engines/__init__.py +56 -0
  8. computer_agent_py-0.2.0/src/computeragent/engines/claude_agent.py +154 -0
  9. computer_agent_py-0.2.0/src/computeragent/engines/git_agent.py +485 -0
  10. computer_agent_py-0.2.0/src/computeragent/harness/__init__.py +303 -0
  11. computer_agent_py-0.2.0/src/computeragent/harness/coordinator.py +721 -0
  12. computer_agent_py-0.2.0/src/computeragent/harness/errors.py +37 -0
  13. computer_agent_py-0.2.0/src/computeragent/harness/usage.py +63 -0
  14. computer_agent_py-0.2.0/src/computeragent/identity/__init__.py +47 -0
  15. computer_agent_py-0.2.0/src/computeragent/identity/gap.py +246 -0
  16. computer_agent_py-0.2.0/src/computeragent/identity/passthrough.py +66 -0
  17. computer_agent_py-0.2.0/src/computeragent/protocol_types/__init__.py +281 -0
  18. computer_agent_py-0.2.0/src/computeragent/session_stores/__init__.py +43 -0
  19. computer_agent_py-0.2.0/src/computeragent/session_stores/memory.py +48 -0
  20. computer_agent_py-0.2.0/src/computeragent/substrates/__init__.py +45 -0
  21. computer_agent_py-0.2.0/src/computeragent/substrates/local.py +126 -0
  22. {computer_agent_py-0.1.1 → computer_agent_py-0.2.0}/src/computeragent/telemetry/config.py +12 -0
  23. {computer_agent_py-0.1.1 → computer_agent_py-0.2.0}/src/computeragent/telemetry/event.py +8 -0
  24. {computer_agent_py-0.1.1 → computer_agent_py-0.2.0}/src/computeragent/telemetry/sinks/agentos.py +90 -7
  25. {computer_agent_py-0.1.1 → computer_agent_py-0.2.0}/src/computeragent/telemetry/sinks/otel.py +157 -0
  26. computer_agent_py-0.1.1/src/computeragent/__init__.py +0 -90
  27. {computer_agent_py-0.1.1 → computer_agent_py-0.2.0}/src/computeragent/_proxy/__init__.py +0 -0
  28. {computer_agent_py-0.1.1 → computer_agent_py-0.2.0}/src/computeragent/policy/__init__.py +0 -0
  29. {computer_agent_py-0.1.1 → computer_agent_py-0.2.0}/src/computeragent/policy/authorizer.py +0 -0
  30. {computer_agent_py-0.1.1 → computer_agent_py-0.2.0}/src/computeragent/policy/cedar.py +0 -0
  31. {computer_agent_py-0.1.1 → computer_agent_py-0.2.0}/src/computeragent/policy/opa.py +0 -0
  32. {computer_agent_py-0.1.1 → computer_agent_py-0.2.0}/src/computeragent/policy/types.py +0 -0
  33. {computer_agent_py-0.1.1 → computer_agent_py-0.2.0}/src/computeragent/py.typed +0 -0
  34. {computer_agent_py-0.1.1 → computer_agent_py-0.2.0}/src/computeragent/telemetry/__init__.py +0 -0
  35. {computer_agent_py-0.1.1 → computer_agent_py-0.2.0}/src/computeragent/telemetry/middleware/__init__.py +0 -0
  36. {computer_agent_py-0.1.1 → computer_agent_py-0.2.0}/src/computeragent/telemetry/middleware/guardrails.py +0 -0
  37. {computer_agent_py-0.1.1 → computer_agent_py-0.2.0}/src/computeragent/telemetry/middleware/pii.py +0 -0
  38. {computer_agent_py-0.1.1 → computer_agent_py-0.2.0}/src/computeragent/telemetry/pipeline.py +0 -0
  39. {computer_agent_py-0.1.1 → computer_agent_py-0.2.0}/src/computeragent/telemetry/sinks/__init__.py +0 -0
  40. {computer_agent_py-0.1.1 → computer_agent_py-0.2.0}/src/computeragent/telemetry/sinks/message_archive.py +0 -0
  41. {computer_agent_py-0.1.1 → computer_agent_py-0.2.0}/src/computeragent/types.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: computer-agent-py
3
- Version: 0.1.1
3
+ Version: 0.2.0
4
4
  Summary: Drop-in replacement for claude-agent-sdk that adds a proxied telemetry pipeline (PII redaction + guardrails) with OpenTelemetry and AgentOS sinks.
5
5
  Keywords: computeragent,claude-agent-sdk,claude,agent,telemetry,otel,opentelemetry,agentos,pii
6
6
  Author: Abhi Bhat
@@ -26,7 +26,14 @@ Requires-Dist: opentelemetry-exporter-otlp>=1.27 ; extra == 'all'
26
26
  Requires-Dist: opentelemetry-semantic-conventions>=0.48b0 ; extra == 'all'
27
27
  Requires-Dist: motor>=3.5 ; extra == 'all'
28
28
  Requires-Dist: cedarpy>=4,<5 ; extra == 'all'
29
+ Requires-Dist: openai>=1,<2 ; extra == 'all'
30
+ Requires-Dist: anthropic>=0.40,<1 ; extra == 'all'
31
+ Requires-Dist: pyyaml>=6,<7 ; extra == 'all'
29
32
  Requires-Dist: cedarpy>=4,<5 ; extra == 'cedar'
33
+ Requires-Dist: pyyaml>=6,<7 ; extra == 'gap'
34
+ Requires-Dist: openai>=1,<2 ; extra == 'gitagent'
35
+ Requires-Dist: anthropic>=0.40,<1 ; extra == 'gitagent'
36
+ Requires-Dist: pyyaml>=6,<7 ; extra == 'gitagent'
30
37
  Requires-Dist: opentelemetry-api>=1.27 ; extra == 'otel'
31
38
  Requires-Dist: opentelemetry-sdk>=1.27 ; extra == 'otel'
32
39
  Requires-Dist: opentelemetry-exporter-otlp>=1.27 ; extra == 'otel'
@@ -38,6 +45,8 @@ Project-URL: Issues, https://github.com/open-gitagent/computer-agent-py/issues
38
45
  Provides-Extra: agentos
39
46
  Provides-Extra: all
40
47
  Provides-Extra: cedar
48
+ Provides-Extra: gap
49
+ Provides-Extra: gitagent
41
50
  Provides-Extra: otel
42
51
  Description-Content-Type: text/markdown
43
52
 
@@ -49,6 +58,8 @@ Description-Content-Type: text/markdown
49
58
 
50
59
  **Drop-in replacement for [`claude-agent-sdk`](https://pypi.org/project/claude-agent-sdk/)** — change the import line, get a proxied telemetry pipeline with PII redaction, configurable OTel export, policy-based tool authorization, and full AgentOS integration for free.
51
60
 
61
+ **New in 0.2.0** — a Python port of the TypeScript [`ComputerAgent`](../ComputerAgent/) harness alongside the drop-in proxy. Same vocabulary (engine / substrate / session-store / identity-loader), `ComputerAgent` + `run_task()` entry points, two built-in engines (`claude-agent-sdk`, `gitagent`). The 0.1.x drop-in surface is untouched — both modes co-exist.
62
+
52
63
  ```diff
53
64
  - from claude_agent_sdk import ClaudeAgentOptions, query
54
65
  - from claude_agent_sdk.types import ResultMessage
@@ -78,11 +89,57 @@ pip install computer-agent-py # core drop-in + OPA policy en
78
89
  pip install 'computer-agent-py[otel]' # + OpenTelemetry sink
79
90
  pip install 'computer-agent-py[agentos]' # + AgentOS Mongo sinks
80
91
  pip install 'computer-agent-py[cedar]' # + Cedar policy engine (in-process)
92
+ pip install 'computer-agent-py[gitagent]' # + gitagent engine (Anthropic + OpenAI dispatch) + GAP loader
93
+ pip install 'computer-agent-py[gap]' # + GAP identity loader only (pyyaml; subset of [gitagent])
81
94
  pip install 'computer-agent-py[all]' # everything
82
95
  ```
83
96
 
84
97
  **Prerequisite** — same as upstream: the `claude` CLI binary on `PATH` and Anthropic / Bedrock credentials in the environment.
85
98
 
99
+ ## Two modes
100
+
101
+ `computer-agent-py` ships two co-existing surfaces. Use whichever fits the call site — they share the same telemetry pipeline and policy authorizer.
102
+
103
+ ### Mode 1 — drop-in proxy (0.1.x, unchanged)
104
+
105
+ The default for callers who want minimum-friction observability over `claude-agent-sdk`. Swap the import line and every `query(...)` / `ClaudeSDKClient(...)` call flows through PII redaction → guardrails → OTel + AgentOS sinks.
106
+
107
+ ```python
108
+ from computeragent import ClaudeAgentOptions, query
109
+
110
+ async for msg in query(prompt="...", options=ClaudeAgentOptions(...)):
111
+ ...
112
+ ```
113
+
114
+ ### Mode 2 — harness (new in 0.2.0)
115
+
116
+ A Python port of the TypeScript [`ComputerAgent`](../ComputerAgent/) stack. Four orthogonal swappable axes — engine / substrate / session-store / identity-loader — plus a `ComputerAgent` class and a `run_task()` convenience.
117
+
118
+ ```python
119
+ from pathlib import Path
120
+ from computeragent import ComputerAgent
121
+
122
+ async with ComputerAgent(
123
+ source=Path.cwd(),
124
+ engine="claude-agent-sdk", # or "gitagent" for OpenAI-compatible endpoints
125
+ runtime="local",
126
+ options={"system_prompt": "You are terse.", "allowed_tools": ["Read", "Glob"]},
127
+ ) as agent:
128
+ result = await agent.chat("List the files in this directory.")
129
+ print(result.messages, result.usage)
130
+ ```
131
+
132
+ Built-ins shipped in 0.2.0:
133
+
134
+ | Axis | Built-ins | Notes |
135
+ |---|---|---|
136
+ | `engine` | `claude-agent-sdk`, `gitagent` | gitagent reads `GITCLAW_MODEL_BASE_URL` + `OPENAI_API_KEY`. MCP-only tools in 0.2.0. |
137
+ | `runtime` (substrate) | `local` | Caller-owned `Path`, or git-clone an `https://` / `git@` `.git` URL into a temp dir. |
138
+ | `session_store` | `memory` | In-process dict. Mongo + SQLite stores in 0.3.0. |
139
+ | `identity_loader` | `passthrough` | Treats inline dicts as manifests. GAP-style loaders in 0.3.0. |
140
+
141
+ Plug-ins register at import time via `register_engine(name, instance)` etc. — third-party engines (e2b substrate, deepagents engine, mongo session store) drop in by implementing the matching Protocol in `computeragent.protocol_types`.
142
+
86
143
  ## Quickstart
87
144
 
88
145
  ```python
@@ -221,7 +278,12 @@ The doc shapes are byte-for-byte compatible with the TypeScript `@open-gitagent/
221
278
 
222
279
  ### Live chat for library-mode agents
223
280
 
224
- When the AgentRegistrySink writes its `agent_registry.source` row, it includes a full inline `IdentitySource` with `files: {agent.yaml, CLAUDE.md}` derived from your `ClaudeAgentOptions`. If a user clicks "New Chat" on the agent in the AgentOS SPA, the harness can clone those files into a sandbox workdir and spawn a live conversation — same UX as hosted (git-sourced) agents. The historical transcript stays available in the Chat tab regardless.
281
+ Two registry shapes for `agent_registry.source`, depending on which mode emitted the event:
282
+
283
+ - **Drop-in proxy (`computeragent.query(...)` / `ClaudeSDKClient`)** writes a `type: "inline"` source with `files: {agent.yaml, CLAUDE.md}` derived from your `ClaudeAgentOptions`. If a user clicks "New Chat" on the agent in the AgentOS SPA, the harness can clone those files into a sandbox workdir and spawn a live conversation — same UX as hosted (git-sourced) agents.
284
+ - **Harness (`ComputerAgent`, new in 0.2.0)** writes a `type: "library"` source. There's no remote `harness-server` to proxy a chat-sandbox spawn to (the agent runs in your Python process), so this shape deliberately fails AgentOS's `hasResolvableSource()` check. The AgentOS UI hides the chat-sandbox button (via the matching `liveChatCapable` derived field on `GET /agents`); historical transcripts remain visible in the Chat tab.
285
+
286
+ Either way, every event still flows through OTel and AgentOS Mongo so the Agents list, Logs tab, and Chat transcript stay populated.
225
287
 
226
288
  ### DocumentDB compatibility
227
289
 
@@ -6,6 +6,8 @@
6
6
 
7
7
  **Drop-in replacement for [`claude-agent-sdk`](https://pypi.org/project/claude-agent-sdk/)** — change the import line, get a proxied telemetry pipeline with PII redaction, configurable OTel export, policy-based tool authorization, and full AgentOS integration for free.
8
8
 
9
+ **New in 0.2.0** — a Python port of the TypeScript [`ComputerAgent`](../ComputerAgent/) harness alongside the drop-in proxy. Same vocabulary (engine / substrate / session-store / identity-loader), `ComputerAgent` + `run_task()` entry points, two built-in engines (`claude-agent-sdk`, `gitagent`). The 0.1.x drop-in surface is untouched — both modes co-exist.
10
+
9
11
  ```diff
10
12
  - from claude_agent_sdk import ClaudeAgentOptions, query
11
13
  - from claude_agent_sdk.types import ResultMessage
@@ -35,11 +37,57 @@ pip install computer-agent-py # core drop-in + OPA policy en
35
37
  pip install 'computer-agent-py[otel]' # + OpenTelemetry sink
36
38
  pip install 'computer-agent-py[agentos]' # + AgentOS Mongo sinks
37
39
  pip install 'computer-agent-py[cedar]' # + Cedar policy engine (in-process)
40
+ pip install 'computer-agent-py[gitagent]' # + gitagent engine (Anthropic + OpenAI dispatch) + GAP loader
41
+ pip install 'computer-agent-py[gap]' # + GAP identity loader only (pyyaml; subset of [gitagent])
38
42
  pip install 'computer-agent-py[all]' # everything
39
43
  ```
40
44
 
41
45
  **Prerequisite** — same as upstream: the `claude` CLI binary on `PATH` and Anthropic / Bedrock credentials in the environment.
42
46
 
47
+ ## Two modes
48
+
49
+ `computer-agent-py` ships two co-existing surfaces. Use whichever fits the call site — they share the same telemetry pipeline and policy authorizer.
50
+
51
+ ### Mode 1 — drop-in proxy (0.1.x, unchanged)
52
+
53
+ The default for callers who want minimum-friction observability over `claude-agent-sdk`. Swap the import line and every `query(...)` / `ClaudeSDKClient(...)` call flows through PII redaction → guardrails → OTel + AgentOS sinks.
54
+
55
+ ```python
56
+ from computeragent import ClaudeAgentOptions, query
57
+
58
+ async for msg in query(prompt="...", options=ClaudeAgentOptions(...)):
59
+ ...
60
+ ```
61
+
62
+ ### Mode 2 — harness (new in 0.2.0)
63
+
64
+ A Python port of the TypeScript [`ComputerAgent`](../ComputerAgent/) stack. Four orthogonal swappable axes — engine / substrate / session-store / identity-loader — plus a `ComputerAgent` class and a `run_task()` convenience.
65
+
66
+ ```python
67
+ from pathlib import Path
68
+ from computeragent import ComputerAgent
69
+
70
+ async with ComputerAgent(
71
+ source=Path.cwd(),
72
+ engine="claude-agent-sdk", # or "gitagent" for OpenAI-compatible endpoints
73
+ runtime="local",
74
+ options={"system_prompt": "You are terse.", "allowed_tools": ["Read", "Glob"]},
75
+ ) as agent:
76
+ result = await agent.chat("List the files in this directory.")
77
+ print(result.messages, result.usage)
78
+ ```
79
+
80
+ Built-ins shipped in 0.2.0:
81
+
82
+ | Axis | Built-ins | Notes |
83
+ |---|---|---|
84
+ | `engine` | `claude-agent-sdk`, `gitagent` | gitagent reads `GITCLAW_MODEL_BASE_URL` + `OPENAI_API_KEY`. MCP-only tools in 0.2.0. |
85
+ | `runtime` (substrate) | `local` | Caller-owned `Path`, or git-clone an `https://` / `git@` `.git` URL into a temp dir. |
86
+ | `session_store` | `memory` | In-process dict. Mongo + SQLite stores in 0.3.0. |
87
+ | `identity_loader` | `passthrough` | Treats inline dicts as manifests. GAP-style loaders in 0.3.0. |
88
+
89
+ Plug-ins register at import time via `register_engine(name, instance)` etc. — third-party engines (e2b substrate, deepagents engine, mongo session store) drop in by implementing the matching Protocol in `computeragent.protocol_types`.
90
+
43
91
  ## Quickstart
44
92
 
45
93
  ```python
@@ -178,7 +226,12 @@ The doc shapes are byte-for-byte compatible with the TypeScript `@open-gitagent/
178
226
 
179
227
  ### Live chat for library-mode agents
180
228
 
181
- When the AgentRegistrySink writes its `agent_registry.source` row, it includes a full inline `IdentitySource` with `files: {agent.yaml, CLAUDE.md}` derived from your `ClaudeAgentOptions`. If a user clicks "New Chat" on the agent in the AgentOS SPA, the harness can clone those files into a sandbox workdir and spawn a live conversation — same UX as hosted (git-sourced) agents. The historical transcript stays available in the Chat tab regardless.
229
+ Two registry shapes for `agent_registry.source`, depending on which mode emitted the event:
230
+
231
+ - **Drop-in proxy (`computeragent.query(...)` / `ClaudeSDKClient`)** writes a `type: "inline"` source with `files: {agent.yaml, CLAUDE.md}` derived from your `ClaudeAgentOptions`. If a user clicks "New Chat" on the agent in the AgentOS SPA, the harness can clone those files into a sandbox workdir and spawn a live conversation — same UX as hosted (git-sourced) agents.
232
+ - **Harness (`ComputerAgent`, new in 0.2.0)** writes a `type: "library"` source. There's no remote `harness-server` to proxy a chat-sandbox spawn to (the agent runs in your Python process), so this shape deliberately fails AgentOS's `hasResolvableSource()` check. The AgentOS UI hides the chat-sandbox button (via the matching `liveChatCapable` derived field on `GET /agents`); historical transcripts remain visible in the Chat tab.
233
+
234
+ Either way, every event still flows through OTel and AgentOS Mongo so the Agents list, Logs tab, and Chat transcript stay populated.
182
235
 
183
236
  ### DocumentDB compatibility
184
237
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "computer-agent-py"
3
- version = "0.1.1"
3
+ version = "0.2.0"
4
4
  description = "Drop-in replacement for claude-agent-sdk that adds a proxied telemetry pipeline (PII redaction + guardrails) with OpenTelemetry and AgentOS sinks."
5
5
  authors = [
6
6
  { name = "Abhi Bhat", email = "abhishek.bhat@lyzr.ai" },
@@ -40,6 +40,14 @@ agentos = [
40
40
  cedar = [
41
41
  "cedarpy>=4,<5",
42
42
  ]
43
+ gitagent = [
44
+ "openai>=1,<2",
45
+ "anthropic>=0.40,<1",
46
+ "pyyaml>=6,<7",
47
+ ]
48
+ gap = [
49
+ "pyyaml>=6,<7",
50
+ ]
43
51
  all = [
44
52
  "opentelemetry-api>=1.27",
45
53
  "opentelemetry-sdk>=1.27",
@@ -47,6 +55,9 @@ all = [
47
55
  "opentelemetry-semantic-conventions>=0.48b0",
48
56
  "motor>=3.5",
49
57
  "cedarpy>=4,<5",
58
+ "openai>=1,<2",
59
+ "anthropic>=0.40,<1",
60
+ "pyyaml>=6,<7",
50
61
  ]
51
62
 
52
63
  [project.urls]
@@ -80,6 +91,9 @@ dev = [
80
91
  "opentelemetry-semantic-conventions>=0.48b0",
81
92
  "motor>=3.5",
82
93
  "cedarpy>=4,<5",
94
+ "openai>=1,<2",
95
+ "anthropic>=0.40,<1",
96
+ "pyyaml>=6,<7",
83
97
  ]
84
98
 
85
99
  [tool.ruff]
@@ -103,10 +117,19 @@ select = [
103
117
  ignore = ["E501"]
104
118
 
105
119
  [tool.ruff.lint.per-file-ignores]
106
- "tests/**" = ["E501", "ASYNC"]
120
+ "tests/**" = ["E501", "ASYNC", "TC001", "TC003"]
107
121
  "examples/**" = ["E501"]
108
122
  "src/computeragent/__init__.py" = ["F401", "F403"]
109
123
  "src/computeragent/types.py" = ["F401", "F403"]
124
+ # 0.2.0 harness layer: protocol value types are referenced from both
125
+ # annotations and runtime code (isinstance, dataclass field defaults).
126
+ # TYPE_CHECKING-only imports would break runtime; ignore the TC nudges.
127
+ "src/computeragent/harness/**" = ["TC001", "TC003", "SIM101"]
128
+ "src/computeragent/engines/**" = ["TC001", "TC003"]
129
+ "src/computeragent/substrates/**" = ["TC003"]
130
+ "src/computeragent/identity/**" = ["TC003"]
131
+ "src/computeragent/session_stores/**" = ["TC001"]
132
+ "src/computeragent/protocol_types/**" = ["TC003"]
110
133
 
111
134
  [tool.mypy]
112
135
  strict = true
@@ -0,0 +1,163 @@
1
+ """``computeragent`` — drop-in replacement for ``claude_agent_sdk``
2
+ PLUS a harness-based agent stack (added in 0.2.0).
3
+
4
+ Every public symbol from ``claude_agent_sdk`` is re-exported here with
5
+ identity preserved (``computeragent.ResultMessage is
6
+ claude_agent_sdk.ResultMessage``). ``query`` and ``ClaudeSDKClient`` are
7
+ wrapped so each message flows through a telemetry pipeline with
8
+ configurable PII redaction / guardrails and pluggable sinks
9
+ (OpenTelemetry, AgentOS registry).
10
+
11
+ Two modes:
12
+
13
+ 1. **Drop-in proxy** (0.1.x, unchanged) — single-import swap::
14
+
15
+ # before
16
+ from claude_agent_sdk import ClaudeAgentOptions, query
17
+ # after
18
+ from computeragent import ClaudeAgentOptions, query
19
+
20
+ 2. **Harness** (new in 0.2.0) — mirror of the TypeScript ComputerAgent
21
+ stack with four orthogonal swappable axes (engine / substrate /
22
+ session-store / identity-loader)::
23
+
24
+ from computeragent import ComputerAgent, run_task
25
+
26
+ async with ComputerAgent(source=Path.cwd()) as agent:
27
+ result = await agent.chat("List the files in this directory")
28
+
29
+ The two modes co-exist. 0.1.x callers see no behaviour change.
30
+ """
31
+
32
+ from __future__ import annotations
33
+
34
+ import claude_agent_sdk as _cas
35
+
36
+ # Re-export every public symbol from the upstream package by walking its
37
+ # `__all__`. This is identity-preserving — `computeragent.ResultMessage is
38
+ # claude_agent_sdk.ResultMessage` evaluates True — so user-side
39
+ # `isinstance(msg, ResultMessage)` keeps working regardless of import origin.
40
+ # Skip `__version__` so our own version (set below) wins.
41
+ _upstream_all = [n for n in getattr(_cas, "__all__", []) if n != "__version__"]
42
+ for _name in _upstream_all:
43
+ globals()[_name] = getattr(_cas, _name)
44
+
45
+ __version__ = "0.2.0"
46
+ upstream_version = getattr(_cas, "__version__", "unknown")
47
+
48
+ # Override the two entry points with the proxied versions. Same signatures,
49
+ # same yielded types — but each yielded message also flows through the
50
+ # telemetry pipeline.
51
+ from ._proxy.client import ClaudeSDKClient # noqa: E402
52
+ from ._proxy.query import query # noqa: E402
53
+
54
+ # Harness surface (new in 0.2.0). Importing pulls the registries which
55
+ # register the built-in engines / substrate / session store / identity
56
+ # loader as a side effect — that's intentional so string-label
57
+ # resolution works without an explicit ``register_*`` call.
58
+ from .harness import ( # noqa: E402
59
+ ChatHandle,
60
+ ChatInput,
61
+ ChatResult,
62
+ ComputerAgent,
63
+ HarnessError,
64
+ HarnessProtocolError,
65
+ UnknownEngineError,
66
+ UnknownIdentityLoaderError,
67
+ UnknownSessionStoreError,
68
+ UnknownSubstrateError,
69
+ UsageRollup,
70
+ run_task,
71
+ )
72
+
73
+ # Policy surface (v0.2 — tool-use authorization). Engines themselves
74
+ # (OpaPolicyEngine, CedarPolicyEngine) stay in ``computeragent.policy`` so
75
+ # users opt into Cedar's extra deliberately.
76
+ from .policy import ( # noqa: E402
77
+ PolicyAction,
78
+ PolicyDecision,
79
+ PolicyEngine,
80
+ PolicyInput,
81
+ PolicyPrincipal,
82
+ PolicyResource,
83
+ PolicyResult,
84
+ PolicyToolAuthorizer,
85
+ )
86
+ from .protocol_types import ( # noqa: E402
87
+ AgentManifest,
88
+ EngineCapabilities,
89
+ EngineContext,
90
+ EngineDriver,
91
+ EngineEvent,
92
+ IdentityLoader,
93
+ IdentitySource,
94
+ PermissionDecision,
95
+ PermissionRequest,
96
+ PolicyDecider,
97
+ SessionStore,
98
+ SessionStoreEntry,
99
+ Substrate,
100
+ SubstrateHandle,
101
+ )
102
+
103
+ # Telemetry surface (additions beyond the drop-in mirror).
104
+ from .telemetry import ( # noqa: E402
105
+ GuardrailFilter,
106
+ PiiRedactor,
107
+ Pipeline,
108
+ TelemetryEvent,
109
+ TelemetryMiddleware,
110
+ TelemetrySink,
111
+ configure,
112
+ get_pipeline,
113
+ shutdown,
114
+ )
115
+
116
+ __all__ = sorted({*_upstream_all, "__version__"}) + [
117
+ # telemetry + policy add-ons (alphabetical at the tail so the public
118
+ # symbol list stays scannable when grepped against `claude_agent_sdk.__all__`)
119
+ "GuardrailFilter",
120
+ "PiiRedactor",
121
+ "Pipeline",
122
+ "PolicyAction",
123
+ "PolicyDecision",
124
+ "PolicyEngine",
125
+ "PolicyInput",
126
+ "PolicyPrincipal",
127
+ "PolicyResource",
128
+ "PolicyResult",
129
+ "PolicyToolAuthorizer",
130
+ "TelemetryEvent",
131
+ "TelemetryMiddleware",
132
+ "TelemetrySink",
133
+ "configure",
134
+ "get_pipeline",
135
+ "shutdown",
136
+ # harness layer (0.2.0)
137
+ "AgentManifest",
138
+ "ChatHandle",
139
+ "ChatInput",
140
+ "ChatResult",
141
+ "ComputerAgent",
142
+ "EngineCapabilities",
143
+ "EngineContext",
144
+ "EngineDriver",
145
+ "EngineEvent",
146
+ "HarnessError",
147
+ "HarnessProtocolError",
148
+ "IdentityLoader",
149
+ "IdentitySource",
150
+ "PermissionDecision",
151
+ "PermissionRequest",
152
+ "PolicyDecider",
153
+ "SessionStore",
154
+ "SessionStoreEntry",
155
+ "Substrate",
156
+ "SubstrateHandle",
157
+ "UnknownEngineError",
158
+ "UnknownIdentityLoaderError",
159
+ "UnknownSessionStoreError",
160
+ "UnknownSubstrateError",
161
+ "UsageRollup",
162
+ "run_task",
163
+ ]
@@ -20,7 +20,7 @@ import claude_agent_sdk
20
20
  from claude_agent_sdk.types import AssistantMessage, ResultMessage, TextBlock
21
21
 
22
22
  from ..telemetry import TelemetryEvent, get_pipeline, new_session_id
23
- from .query import _derive_agent_name
23
+ from .query import _derive_agent_description, _derive_agent_name
24
24
 
25
25
  if TYPE_CHECKING:
26
26
  from collections.abc import AsyncIterable, AsyncIterator
@@ -50,6 +50,7 @@ class ClaudeSDKClient:
50
50
  # placeholder-vs-upstream rationale).
51
51
  self._upstream_session_id: str | None = None
52
52
  self._agent_name = _derive_agent_name(options)
53
+ self._agent_description = _derive_agent_description(options)
53
54
  self._started_at: float | None = None
54
55
  self._last_result: ResultMessage | None = None
55
56
  self._last_assistant_text: str = ""
@@ -75,6 +76,7 @@ class ClaudeSDKClient:
75
76
  TelemetryEvent.session_started(
76
77
  session_id=self._session_id,
77
78
  agent_name=self._agent_name,
79
+ agent_description=self._agent_description,
78
80
  options=self._options,
79
81
  prompt=prompt if prompt is not None else "<connect>",
80
82
  )
@@ -122,6 +124,8 @@ class ClaudeSDKClient:
122
124
  for ev in TelemetryEvent.from_message(
123
125
  msg, session_id=self._session_id, agent_name=self._agent_name
124
126
  ):
127
+ if self._agent_description is not None:
128
+ ev.agent_description = self._agent_description
125
129
  await pipeline.emit(ev)
126
130
  if isinstance(msg, AssistantMessage):
127
131
  for block in getattr(msg, "content", []) or []:
@@ -145,6 +149,8 @@ class ClaudeSDKClient:
145
149
  for ev in TelemetryEvent.from_message(
146
150
  msg, session_id=self._session_id, agent_name=self._agent_name
147
151
  ):
152
+ if self._agent_description is not None:
153
+ ev.agent_description = self._agent_description
148
154
  await pipeline.emit(ev)
149
155
  if isinstance(msg, AssistantMessage):
150
156
  for block in getattr(msg, "content", []) or []:
@@ -203,6 +209,7 @@ class ClaudeSDKClient:
203
209
  duration_ms=duration_ms,
204
210
  last_assistant_text=self._last_assistant_text,
205
211
  )
212
+ ev.agent_description = self._agent_description
206
213
  if self._upstream_session_id is not None:
207
214
  ev.payload["upstream_session_id"] = self._upstream_session_id
208
215
  await pipeline.emit(ev)
@@ -212,6 +219,7 @@ class ClaudeSDKClient:
212
219
  kind="session_ended",
213
220
  session_id=self._session_id,
214
221
  agent_name=self._agent_name,
222
+ agent_description=self._agent_description,
215
223
  payload={
216
224
  "is_error": False,
217
225
  "subtype": "no_result",
@@ -39,6 +39,7 @@ async def query( # noqa: PLR0912 - mirror upstream's tolerant param surface
39
39
  pipeline = get_pipeline()
40
40
  session_id = new_session_id()
41
41
  agent_name = _derive_agent_name(options)
42
+ agent_description = _derive_agent_description(options)
42
43
  started_at = time.monotonic()
43
44
  last_assistant_text = ""
44
45
 
@@ -47,6 +48,7 @@ async def query( # noqa: PLR0912 - mirror upstream's tolerant param surface
47
48
  TelemetryEvent.session_started(
48
49
  session_id=session_id,
49
50
  agent_name=agent_name,
51
+ agent_description=agent_description,
50
52
  options=options,
51
53
  prompt=prompt,
52
54
  )
@@ -78,6 +80,11 @@ async def query( # noqa: PLR0912 - mirror upstream's tolerant param surface
78
80
  for ev in TelemetryEvent.from_message(
79
81
  msg, session_id=session_id, agent_name=agent_name
80
82
  ):
83
+ # Carry the description forward on every event so sinks that
84
+ # group by description (or want to surface it on per-message
85
+ # telemetry) don't have to back-reference session_started.
86
+ if agent_description is not None:
87
+ ev.agent_description = agent_description
81
88
  await pipeline.emit(ev)
82
89
 
83
90
  if isinstance(msg, AssistantMessage):
@@ -100,6 +107,7 @@ async def query( # noqa: PLR0912 - mirror upstream's tolerant param surface
100
107
  duration_ms=duration_ms,
101
108
  last_assistant_text=last_assistant_text,
102
109
  )
110
+ ev.agent_description = agent_description
103
111
  if upstream_session_id is not None:
104
112
  ev.payload["upstream_session_id"] = upstream_session_id
105
113
  await pipeline.emit(ev)
@@ -110,6 +118,7 @@ async def query( # noqa: PLR0912 - mirror upstream's tolerant param surface
110
118
  kind="session_ended",
111
119
  session_id=session_id,
112
120
  agent_name=agent_name,
121
+ agent_description=agent_description,
113
122
  payload={
114
123
  "is_error": False,
115
124
  "subtype": "no_result",
@@ -124,14 +133,14 @@ async def query( # noqa: PLR0912 - mirror upstream's tolerant param surface
124
133
  except BaseException as exc:
125
134
  duration_ms = (time.monotonic() - started_at) * 1000.0
126
135
  try:
127
- await pipeline.emit(
128
- TelemetryEvent.session_ended_error(
129
- session_id=session_id,
130
- agent_name=agent_name,
131
- exc=exc,
132
- duration_ms=duration_ms,
133
- )
136
+ err_ev = TelemetryEvent.session_ended_error(
137
+ session_id=session_id,
138
+ agent_name=agent_name,
139
+ exc=exc,
140
+ duration_ms=duration_ms,
134
141
  )
142
+ err_ev.agent_description = agent_description
143
+ await pipeline.emit(err_ev)
135
144
  except Exception: # noqa: BLE001
136
145
  logger.debug("error-path telemetry emit failed", exc_info=True)
137
146
  raise
@@ -163,3 +172,20 @@ def _derive_agent_name(options: Any) -> str | None:
163
172
  if first:
164
173
  return first[:80]
165
174
  return None
175
+
176
+
177
+ def _derive_agent_description(options: Any) -> str | None:
178
+ """Pick an agent description from options.
179
+
180
+ Mirrors :func:`_derive_agent_name` — the description is purely opt-in.
181
+ Callers set it on options via ``options.agent_description = "..."`` and
182
+ the proxy threads it through to the telemetry pipeline. Unlike the
183
+ name, there's no implicit fallback to system_prompt: a description is
184
+ a human label, not something we want to synthesize from the prompt.
185
+ """
186
+ if options is None:
187
+ return None
188
+ desc = getattr(options, "agent_description", None)
189
+ if isinstance(desc, str) and desc:
190
+ return desc
191
+ return None
@@ -0,0 +1,56 @@
1
+ """Engine registry — string labels → :class:`EngineDriver` instances.
2
+
3
+ Built-ins shipped in 0.2.0:
4
+
5
+ * ``"claude-agent-sdk"`` — wraps ``claude_agent_sdk.query`` directly.
6
+ * ``"gitagent"`` — Python re-implementation of the gitclaw loop over an
7
+ OpenAI-compatible endpoint. Requires the ``[gitagent]`` extra (openai).
8
+ The class is exposed eagerly but the OpenAI client is constructed only
9
+ on first ``start_session`` — registering the engine never fails on
10
+ install-time missing extras.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import logging
16
+
17
+ from ..harness.errors import UnknownEngineError
18
+ from ..protocol_types import EngineDriver
19
+ from .claude_agent import ClaudeAgentEngine
20
+ from .git_agent import GitAgentEngine
21
+
22
+ logger = logging.getLogger("computeragent.engines")
23
+
24
+ _REGISTRY: dict[str, EngineDriver] = {}
25
+
26
+
27
+ def register_engine(name: str, engine: EngineDriver) -> None:
28
+ if not isinstance(engine, EngineDriver):
29
+ raise TypeError(
30
+ f"register_engine({name!r}): instance does not satisfy "
31
+ "the EngineDriver Protocol"
32
+ )
33
+ _REGISTRY[name] = engine
34
+ logger.debug("registered engine %r", name)
35
+
36
+
37
+ def resolve_engine(name: str) -> EngineDriver:
38
+ try:
39
+ return _REGISTRY[name]
40
+ except KeyError as exc:
41
+ raise UnknownEngineError(
42
+ f"unknown engine {name!r}; registered: {sorted(_REGISTRY)!r}"
43
+ ) from exc
44
+
45
+
46
+ # Built-ins.
47
+ register_engine("claude-agent-sdk", ClaudeAgentEngine())
48
+ register_engine("gitagent", GitAgentEngine())
49
+
50
+
51
+ __all__ = [
52
+ "ClaudeAgentEngine",
53
+ "GitAgentEngine",
54
+ "register_engine",
55
+ "resolve_engine",
56
+ ]