steerable-agent-harness 0.2.0__py3-none-any.whl
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.
- steerable_agent_harness/__init__.py +21 -0
- steerable_agent_harness/budget.py +38 -0
- steerable_agent_harness/completion.py +13 -0
- steerable_agent_harness/generated.py +159 -0
- steerable_agent_harness/policy.py +25 -0
- steerable_agent_harness/retry.py +21 -0
- steerable_agent_harness/tracing.py +20 -0
- steerable_agent_harness-0.2.0.dist-info/METADATA +12 -0
- steerable_agent_harness-0.2.0.dist-info/RECORD +11 -0
- steerable_agent_harness-0.2.0.dist-info/WHEEL +5 -0
- steerable_agent_harness-0.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from .policy import ToolMode, PolicyDecision, decide_tool_mode
|
|
2
|
+
from .budget import BudgetState, BudgetLimit, consume_budget
|
|
3
|
+
from .retry import RetryPolicy, next_retry_delay_ms
|
|
4
|
+
from .completion import is_terminal_result
|
|
5
|
+
from .tracing import TraceSpan
|
|
6
|
+
|
|
7
|
+
__version__ = "0.2.0"
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"__version__",
|
|
11
|
+
"ToolMode",
|
|
12
|
+
"PolicyDecision",
|
|
13
|
+
"decide_tool_mode",
|
|
14
|
+
"BudgetState",
|
|
15
|
+
"BudgetLimit",
|
|
16
|
+
"consume_budget",
|
|
17
|
+
"RetryPolicy",
|
|
18
|
+
"next_retry_delay_ms",
|
|
19
|
+
"is_terminal_result",
|
|
20
|
+
"TraceSpan",
|
|
21
|
+
]
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass(slots=True)
|
|
7
|
+
class BudgetLimit:
|
|
8
|
+
max_tokens: int
|
|
9
|
+
max_steps: int
|
|
10
|
+
max_tool_calls: int
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass(slots=True)
|
|
14
|
+
class BudgetState:
|
|
15
|
+
tokens_used: int = 0
|
|
16
|
+
steps_used: int = 0
|
|
17
|
+
tool_calls_used: int = 0
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def consume_budget(
|
|
21
|
+
state: BudgetState,
|
|
22
|
+
limits: BudgetLimit,
|
|
23
|
+
*,
|
|
24
|
+
tokens: int = 0,
|
|
25
|
+
step: bool = False,
|
|
26
|
+
tool_call: bool = False,
|
|
27
|
+
) -> tuple[BudgetState, bool]:
|
|
28
|
+
next_state = BudgetState(
|
|
29
|
+
tokens_used=state.tokens_used + max(tokens, 0),
|
|
30
|
+
steps_used=state.steps_used + (1 if step else 0),
|
|
31
|
+
tool_calls_used=state.tool_calls_used + (1 if tool_call else 0),
|
|
32
|
+
)
|
|
33
|
+
exhausted = (
|
|
34
|
+
next_state.tokens_used > limits.max_tokens
|
|
35
|
+
or next_state.steps_used > limits.max_steps
|
|
36
|
+
or next_state.tool_calls_used > limits.max_tool_calls
|
|
37
|
+
)
|
|
38
|
+
return next_state, exhausted
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def is_terminal_result(result: dict[str, Any] | None) -> bool:
|
|
7
|
+
if not result:
|
|
8
|
+
return False
|
|
9
|
+
if result.get("terminal") is True:
|
|
10
|
+
return True
|
|
11
|
+
if result.get("success") is False and result.get("needsFollowup") is not True:
|
|
12
|
+
return True
|
|
13
|
+
return False
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
from pydantic import BaseModel
|
|
5
|
+
|
|
6
|
+
class ChatAgent(BaseModel):
|
|
7
|
+
id: str
|
|
8
|
+
slug: str | None = None
|
|
9
|
+
name: str
|
|
10
|
+
icon: str | None = None
|
|
11
|
+
color: str | None = None
|
|
12
|
+
description: str | None = None
|
|
13
|
+
rolePrompt: str | None = None
|
|
14
|
+
forbiddenPrompt: str | None = None
|
|
15
|
+
skillIds: list[Any] | None = None
|
|
16
|
+
allowExternalSkills: bool | None = None
|
|
17
|
+
isBuiltin: bool | None = None
|
|
18
|
+
isArchived: bool | None = None
|
|
19
|
+
sortOrder: int | None = None
|
|
20
|
+
createdAt: str
|
|
21
|
+
updatedAt: str
|
|
22
|
+
|
|
23
|
+
class ChatMessage(BaseModel):
|
|
24
|
+
id: str
|
|
25
|
+
chatId: str | None = None
|
|
26
|
+
role: str
|
|
27
|
+
content: str
|
|
28
|
+
agentId: str | None = None
|
|
29
|
+
toolCalls: list[Any] | None = None
|
|
30
|
+
toolResult: Any | None = None
|
|
31
|
+
createdAt: str
|
|
32
|
+
updatedAt: str | None = None
|
|
33
|
+
|
|
34
|
+
class SSEEvent(BaseModel):
|
|
35
|
+
type: str
|
|
36
|
+
event: str | None = None
|
|
37
|
+
content: str | None = None
|
|
38
|
+
hint: str | None = None
|
|
39
|
+
message: str | None = None
|
|
40
|
+
code: str | None = None
|
|
41
|
+
orchestrationGroupId: str | None = None
|
|
42
|
+
taskId: str | None = None
|
|
43
|
+
messageId: str | None = None
|
|
44
|
+
payload: dict[str, Any] | None = None
|
|
45
|
+
|
|
46
|
+
class AgentSession(BaseModel):
|
|
47
|
+
id: str | None = None
|
|
48
|
+
sessionId: str
|
|
49
|
+
userId: str
|
|
50
|
+
projectId: Any | None = None
|
|
51
|
+
chatId: str
|
|
52
|
+
currentStage: str
|
|
53
|
+
nextStage: Any | None = None
|
|
54
|
+
scenario: str | None = None
|
|
55
|
+
stageData: Any | None = None
|
|
56
|
+
isActive: bool
|
|
57
|
+
createdAt: str
|
|
58
|
+
updatedAt: str
|
|
59
|
+
|
|
60
|
+
class HarnessTrace(BaseModel):
|
|
61
|
+
traceId: str
|
|
62
|
+
userId: Any | None = None
|
|
63
|
+
chatId: Any | None = None
|
|
64
|
+
sessionId: Any | None = None
|
|
65
|
+
assistantMessageId: Any | None = None
|
|
66
|
+
status: str
|
|
67
|
+
durationMs: Any | None = None
|
|
68
|
+
hadError: bool
|
|
69
|
+
errorMessage: Any | None = None
|
|
70
|
+
eventCount: int
|
|
71
|
+
spanCount: int
|
|
72
|
+
totalTokens: Any | None = None
|
|
73
|
+
modelId: Any | None = None
|
|
74
|
+
startedAtMs: Any | None = None
|
|
75
|
+
createdAt: str
|
|
76
|
+
updatedAt: str
|
|
77
|
+
|
|
78
|
+
class TraceEvent(BaseModel):
|
|
79
|
+
id: str | None = None
|
|
80
|
+
traceId: str
|
|
81
|
+
kind: str
|
|
82
|
+
name: str
|
|
83
|
+
sequence: int
|
|
84
|
+
timestampMs: int
|
|
85
|
+
durationMs: Any | None = None
|
|
86
|
+
status: Any | None = None
|
|
87
|
+
payload: Any | None = None
|
|
88
|
+
createdAt: str | None = None
|
|
89
|
+
|
|
90
|
+
class TraceSpan(BaseModel):
|
|
91
|
+
spanId: str
|
|
92
|
+
traceId: Any | None = None
|
|
93
|
+
parentSpanId: Any | None = None
|
|
94
|
+
name: str
|
|
95
|
+
kind: str | None = None
|
|
96
|
+
startMs: int
|
|
97
|
+
endMs: Any | None = None
|
|
98
|
+
durationMs: Any | None = None
|
|
99
|
+
status: str
|
|
100
|
+
attrs: dict[str, Any] | None = None
|
|
101
|
+
|
|
102
|
+
class CommandSafetyPattern(BaseModel):
|
|
103
|
+
id: str
|
|
104
|
+
label: str
|
|
105
|
+
description: str
|
|
106
|
+
pattern: str
|
|
107
|
+
category: str
|
|
108
|
+
severity: str
|
|
109
|
+
platform: str
|
|
110
|
+
|
|
111
|
+
class SidecarError(BaseModel):
|
|
112
|
+
code: int
|
|
113
|
+
message: str
|
|
114
|
+
data: Any | None = None
|
|
115
|
+
kind: str | None = None
|
|
116
|
+
|
|
117
|
+
class SidecarHealth(BaseModel):
|
|
118
|
+
status: str
|
|
119
|
+
version: str
|
|
120
|
+
protocolVersion: str | None = None
|
|
121
|
+
uptimeMs: int
|
|
122
|
+
pid: int | None = None
|
|
123
|
+
pythonVersion: str | None = None
|
|
124
|
+
platform: str | None = None
|
|
125
|
+
loadedProviders: list[Any] | None = None
|
|
126
|
+
loadedTools: int | None = None
|
|
127
|
+
activeTraces: int | None = None
|
|
128
|
+
checks: dict[str, Any] | None = None
|
|
129
|
+
|
|
130
|
+
class SidecarNotification(BaseModel):
|
|
131
|
+
jsonrpc: str
|
|
132
|
+
method: str
|
|
133
|
+
params: Any | None = None
|
|
134
|
+
|
|
135
|
+
class SidecarRequest(BaseModel):
|
|
136
|
+
jsonrpc: str
|
|
137
|
+
id: Any
|
|
138
|
+
method: str
|
|
139
|
+
params: Any | None = None
|
|
140
|
+
|
|
141
|
+
class SidecarResponse(BaseModel):
|
|
142
|
+
jsonrpc: str
|
|
143
|
+
id: Any
|
|
144
|
+
result: Any | None = None
|
|
145
|
+
error: Any | None = None
|
|
146
|
+
|
|
147
|
+
class ToolCall(BaseModel):
|
|
148
|
+
id: str
|
|
149
|
+
name: str
|
|
150
|
+
arguments: dict[str, Any]
|
|
151
|
+
|
|
152
|
+
class ToolResult(BaseModel):
|
|
153
|
+
success: bool
|
|
154
|
+
terminal: bool | None = None
|
|
155
|
+
needsFollowup: bool | None = None
|
|
156
|
+
nextAction: str | None = None
|
|
157
|
+
message: str | None = None
|
|
158
|
+
error: str | None = None
|
|
159
|
+
data: dict[str, Any] | None = None
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Literal
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
ToolMode = Literal["read", "safe_write", "destructive", "other"]
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(slots=True)
|
|
11
|
+
class PolicyDecision:
|
|
12
|
+
allowed: bool
|
|
13
|
+
tool_mode: ToolMode
|
|
14
|
+
reason: str
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def decide_tool_mode(tool_name: str) -> ToolMode:
|
|
18
|
+
normalized = tool_name.lower()
|
|
19
|
+
if normalized.startswith(("get_", "list_", "read_")):
|
|
20
|
+
return "read"
|
|
21
|
+
if normalized.startswith(("create_", "update_", "set_", "write_", "apply_")):
|
|
22
|
+
return "safe_write"
|
|
23
|
+
if normalized.startswith(("delete_", "drop_", "remove_", "destroy_")):
|
|
24
|
+
return "destructive"
|
|
25
|
+
return "other"
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
import random
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass(slots=True)
|
|
8
|
+
class RetryPolicy:
|
|
9
|
+
max_attempts: int = 3
|
|
10
|
+
base_delay_ms: int = 200
|
|
11
|
+
max_delay_ms: int = 5000
|
|
12
|
+
jitter: bool = True
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def next_retry_delay_ms(policy: RetryPolicy, attempt: int) -> int:
|
|
16
|
+
if attempt < 1:
|
|
17
|
+
attempt = 1
|
|
18
|
+
delay = min(policy.base_delay_ms * (2 ** (attempt - 1)), policy.max_delay_ms)
|
|
19
|
+
if policy.jitter:
|
|
20
|
+
delay = int(delay * random.uniform(0.8, 1.2))
|
|
21
|
+
return max(delay, 0)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass(slots=True)
|
|
9
|
+
class TraceSpan:
|
|
10
|
+
span_id: str
|
|
11
|
+
name: str
|
|
12
|
+
start_at: str = field(
|
|
13
|
+
default_factory=lambda: datetime.now(timezone.utc).isoformat()
|
|
14
|
+
)
|
|
15
|
+
end_at: str | None = None
|
|
16
|
+
attrs: dict[str, Any] = field(default_factory=dict)
|
|
17
|
+
|
|
18
|
+
def finish(self) -> None:
|
|
19
|
+
if self.end_at is None:
|
|
20
|
+
self.end_at = datetime.now(timezone.utc).isoformat()
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: steerable-agent-harness
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Steerable harness helpers for Python
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: pydantic>=2.10.0
|
|
8
|
+
Requires-Dist: steerable-agent-protocol<1.0.0,>=0.1.0
|
|
9
|
+
|
|
10
|
+
# steerable-agent-harness
|
|
11
|
+
|
|
12
|
+
Python harness primitives and policy helpers for Steerable.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
steerable_agent_harness/__init__.py,sha256=iexmxAnppYLxK7_KuC-O5wa3j0SIKW5D-R6d2Qz8tek,518
|
|
2
|
+
steerable_agent_harness/budget.py,sha256=Cx38m3ifbbjVtG-FJPKpY4TrBsgS5AOTOaVGybkbUvk,927
|
|
3
|
+
steerable_agent_harness/completion.py,sha256=FMqUyfyUssFmZXwA7ohwWMqj93gsXYsyd1VdfkZxCl0,343
|
|
4
|
+
steerable_agent_harness/generated.py,sha256=PYEMt4BkfH0T8ret99arwkljF_F3J4rXaElKxEwqzGg,3826
|
|
5
|
+
steerable_agent_harness/policy.py,sha256=L4oXfmMgKXgsbr_Q1-7uaeYf6AVzy5LTKWmVaNyH0IM,669
|
|
6
|
+
steerable_agent_harness/retry.py,sha256=UKDAfpKCc92oWABQb2rPepeCg3RYt45X3lHL18OGMQo,528
|
|
7
|
+
steerable_agent_harness/tracing.py,sha256=ru4Y7acHArbdC8aSaYJrzfXioCFcQQY2E-uaZ4ATTuM,531
|
|
8
|
+
steerable_agent_harness-0.2.0.dist-info/METADATA,sha256=3htTtG8Kc-gx97SGUpHZOCoT8TM1VX9VY8if0vJ3tHI,351
|
|
9
|
+
steerable_agent_harness-0.2.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
10
|
+
steerable_agent_harness-0.2.0.dist-info/top_level.txt,sha256=L99GRcOHaeL_gakhZZnmpEbK5yV4fOeIxXzWKW_2lDA,24
|
|
11
|
+
steerable_agent_harness-0.2.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
steerable_agent_harness
|