supyagent 0.1.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.
Potentially problematic release.
This version of supyagent might be problematic. Click here for more details.
- supyagent/__init__.py +5 -0
- supyagent/__main__.py +8 -0
- supyagent/cli/__init__.py +1 -0
- supyagent/cli/main.py +946 -0
- supyagent/core/__init__.py +21 -0
- supyagent/core/agent.py +379 -0
- supyagent/core/context.py +158 -0
- supyagent/core/credentials.py +275 -0
- supyagent/core/delegation.py +286 -0
- supyagent/core/executor.py +232 -0
- supyagent/core/llm.py +73 -0
- supyagent/core/registry.py +238 -0
- supyagent/core/session_manager.py +233 -0
- supyagent/core/tools.py +235 -0
- supyagent/models/__init__.py +6 -0
- supyagent/models/agent_config.py +86 -0
- supyagent/models/session.py +43 -0
- supyagent/utils/__init__.py +1 -0
- supyagent/utils/paths.py +31 -0
- supyagent-0.1.0.dist-info/METADATA +328 -0
- supyagent-0.1.0.dist-info/RECORD +24 -0
- supyagent-0.1.0.dist-info/WHEEL +4 -0
- supyagent-0.1.0.dist-info/entry_points.txt +2 -0
- supyagent-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Credential manager for secure storage and retrieval of API keys and tokens.
|
|
3
|
+
|
|
4
|
+
Credentials are encrypted using Fernet (AES-128-CBC) and stored per-agent.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import getpass
|
|
8
|
+
import json
|
|
9
|
+
import os
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from cryptography.fernet import Fernet, InvalidToken
|
|
13
|
+
from rich.console import Console
|
|
14
|
+
from rich.panel import Panel
|
|
15
|
+
from rich.prompt import Confirm
|
|
16
|
+
|
|
17
|
+
console = Console()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class CredentialManager:
|
|
21
|
+
"""
|
|
22
|
+
Manages secure credential storage and retrieval.
|
|
23
|
+
|
|
24
|
+
Features:
|
|
25
|
+
- Encrypted storage using Fernet (AES-128-CBC)
|
|
26
|
+
- Per-agent credential isolation
|
|
27
|
+
- Environment variable fallback
|
|
28
|
+
- Interactive prompting with hidden input
|
|
29
|
+
- Persistence choice for users
|
|
30
|
+
|
|
31
|
+
Directory structure:
|
|
32
|
+
.supyagent/credentials/.key # Encryption key (600 permissions)
|
|
33
|
+
.supyagent/credentials/<agent>.enc # Encrypted credentials
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(self, base_dir: Path | None = None):
|
|
37
|
+
"""
|
|
38
|
+
Initialize the credential manager.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
base_dir: Base directory for credential storage
|
|
42
|
+
"""
|
|
43
|
+
if base_dir is None:
|
|
44
|
+
base_dir = Path(".supyagent/credentials")
|
|
45
|
+
self.base_dir = base_dir
|
|
46
|
+
self.base_dir.mkdir(parents=True, exist_ok=True)
|
|
47
|
+
self._fernet = self._get_fernet()
|
|
48
|
+
self._cache: dict[str, dict[str, str]] = {}
|
|
49
|
+
|
|
50
|
+
def _get_fernet(self) -> Fernet:
|
|
51
|
+
"""Get or create the encryption key."""
|
|
52
|
+
key_file = self.base_dir / ".key"
|
|
53
|
+
|
|
54
|
+
if key_file.exists():
|
|
55
|
+
key = key_file.read_bytes()
|
|
56
|
+
else:
|
|
57
|
+
key = Fernet.generate_key()
|
|
58
|
+
key_file.write_bytes(key)
|
|
59
|
+
# Set restrictive permissions (owner read/write only)
|
|
60
|
+
try:
|
|
61
|
+
key_file.chmod(0o600)
|
|
62
|
+
except OSError:
|
|
63
|
+
pass # Windows doesn't support chmod the same way
|
|
64
|
+
|
|
65
|
+
return Fernet(key)
|
|
66
|
+
|
|
67
|
+
def _cred_path(self, agent: str) -> Path:
|
|
68
|
+
"""Get the path to an agent's credential file."""
|
|
69
|
+
return self.base_dir / f"{agent}.enc"
|
|
70
|
+
|
|
71
|
+
def _load_credentials(self, agent: str) -> dict[str, str]:
|
|
72
|
+
"""
|
|
73
|
+
Load and decrypt credentials for an agent.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
agent: Agent name
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
Dict of credential name -> value
|
|
80
|
+
"""
|
|
81
|
+
if agent in self._cache:
|
|
82
|
+
return self._cache[agent]
|
|
83
|
+
|
|
84
|
+
path = self._cred_path(agent)
|
|
85
|
+
if not path.exists():
|
|
86
|
+
self._cache[agent] = {}
|
|
87
|
+
return {}
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
encrypted = path.read_bytes()
|
|
91
|
+
decrypted = self._fernet.decrypt(encrypted)
|
|
92
|
+
creds = json.loads(decrypted)
|
|
93
|
+
self._cache[agent] = creds
|
|
94
|
+
return creds
|
|
95
|
+
except (InvalidToken, json.JSONDecodeError):
|
|
96
|
+
# Corrupted or invalid file
|
|
97
|
+
self._cache[agent] = {}
|
|
98
|
+
return {}
|
|
99
|
+
|
|
100
|
+
def _save_credentials(self, agent: str, creds: dict[str, str]) -> None:
|
|
101
|
+
"""
|
|
102
|
+
Encrypt and save credentials.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
agent: Agent name
|
|
106
|
+
creds: Dict of credentials to save
|
|
107
|
+
"""
|
|
108
|
+
encrypted = self._fernet.encrypt(json.dumps(creds).encode())
|
|
109
|
+
path = self._cred_path(agent)
|
|
110
|
+
path.write_bytes(encrypted)
|
|
111
|
+
# Set restrictive permissions
|
|
112
|
+
try:
|
|
113
|
+
path.chmod(0o600)
|
|
114
|
+
except OSError:
|
|
115
|
+
pass
|
|
116
|
+
self._cache[agent] = creds
|
|
117
|
+
|
|
118
|
+
def get(self, agent: str, name: str) -> str | None:
|
|
119
|
+
"""
|
|
120
|
+
Get a credential value.
|
|
121
|
+
|
|
122
|
+
Checks in order:
|
|
123
|
+
1. Environment variables
|
|
124
|
+
2. Stored credentials
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
agent: Agent name
|
|
128
|
+
name: Credential name (e.g., "OPENAI_API_KEY")
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
Credential value or None if not found
|
|
132
|
+
"""
|
|
133
|
+
# First check environment
|
|
134
|
+
if name in os.environ:
|
|
135
|
+
return os.environ[name]
|
|
136
|
+
|
|
137
|
+
# Then check stored credentials
|
|
138
|
+
creds = self._load_credentials(agent)
|
|
139
|
+
return creds.get(name)
|
|
140
|
+
|
|
141
|
+
def set(
|
|
142
|
+
self,
|
|
143
|
+
agent: str,
|
|
144
|
+
name: str,
|
|
145
|
+
value: str,
|
|
146
|
+
persist: bool = True,
|
|
147
|
+
) -> None:
|
|
148
|
+
"""
|
|
149
|
+
Set a credential value.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
agent: Agent name
|
|
153
|
+
name: Credential name
|
|
154
|
+
value: Credential value
|
|
155
|
+
persist: If True, save to encrypted storage. If False, only set in environment.
|
|
156
|
+
"""
|
|
157
|
+
if persist:
|
|
158
|
+
creds = self._load_credentials(agent)
|
|
159
|
+
creds[name] = value
|
|
160
|
+
self._save_credentials(agent, creds)
|
|
161
|
+
else:
|
|
162
|
+
# Session-only: just set in environment
|
|
163
|
+
os.environ[name] = value
|
|
164
|
+
|
|
165
|
+
def has(self, agent: str, name: str) -> bool:
|
|
166
|
+
"""
|
|
167
|
+
Check if a credential exists.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
agent: Agent name
|
|
171
|
+
name: Credential name
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
True if credential exists
|
|
175
|
+
"""
|
|
176
|
+
return self.get(agent, name) is not None
|
|
177
|
+
|
|
178
|
+
def prompt_for_credential(
|
|
179
|
+
self,
|
|
180
|
+
name: str,
|
|
181
|
+
description: str,
|
|
182
|
+
service: str | None = None,
|
|
183
|
+
) -> tuple[str, bool] | None:
|
|
184
|
+
"""
|
|
185
|
+
Interactively prompt user for a credential.
|
|
186
|
+
|
|
187
|
+
Displays a nice panel and uses hidden input for the value.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
name: Credential name
|
|
191
|
+
description: Why this credential is needed
|
|
192
|
+
service: Optional service name
|
|
193
|
+
|
|
194
|
+
Returns:
|
|
195
|
+
Tuple of (value, should_persist) or None if user skipped
|
|
196
|
+
"""
|
|
197
|
+
console.print()
|
|
198
|
+
|
|
199
|
+
# Build the panel content
|
|
200
|
+
service_line = f"\nService: [cyan]{service}[/cyan]" if service else ""
|
|
201
|
+
content = f"[bold]{name}[/bold]{service_line}\n\n{description}"
|
|
202
|
+
|
|
203
|
+
console.print(
|
|
204
|
+
Panel(
|
|
205
|
+
content,
|
|
206
|
+
title="🔑 Credential Required",
|
|
207
|
+
border_style="yellow",
|
|
208
|
+
)
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
# Get the value with hidden input
|
|
212
|
+
value = getpass.getpass("Enter value (or press Enter to skip): ")
|
|
213
|
+
|
|
214
|
+
if not value:
|
|
215
|
+
console.print("[dim]Skipped[/dim]")
|
|
216
|
+
return None
|
|
217
|
+
|
|
218
|
+
# Ask about persistence
|
|
219
|
+
persist = Confirm.ask("Save for future sessions?", default=True)
|
|
220
|
+
|
|
221
|
+
return value, persist
|
|
222
|
+
|
|
223
|
+
def list_credentials(self, agent: str) -> list[str]:
|
|
224
|
+
"""
|
|
225
|
+
List stored credential names for an agent.
|
|
226
|
+
|
|
227
|
+
Args:
|
|
228
|
+
agent: Agent name
|
|
229
|
+
|
|
230
|
+
Returns:
|
|
231
|
+
List of credential names
|
|
232
|
+
"""
|
|
233
|
+
creds = self._load_credentials(agent)
|
|
234
|
+
return list(creds.keys())
|
|
235
|
+
|
|
236
|
+
def delete(self, agent: str, name: str) -> bool:
|
|
237
|
+
"""
|
|
238
|
+
Delete a stored credential.
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
agent: Agent name
|
|
242
|
+
name: Credential name
|
|
243
|
+
|
|
244
|
+
Returns:
|
|
245
|
+
True if deleted, False if not found
|
|
246
|
+
"""
|
|
247
|
+
creds = self._load_credentials(agent)
|
|
248
|
+
if name in creds:
|
|
249
|
+
del creds[name]
|
|
250
|
+
self._save_credentials(agent, creds)
|
|
251
|
+
return True
|
|
252
|
+
return False
|
|
253
|
+
|
|
254
|
+
def get_all_for_tools(self, agent: str) -> dict[str, str]:
|
|
255
|
+
"""
|
|
256
|
+
Get all credentials for tool execution.
|
|
257
|
+
|
|
258
|
+
Combines environment variables with stored credentials
|
|
259
|
+
(environment takes precedence).
|
|
260
|
+
|
|
261
|
+
Args:
|
|
262
|
+
agent: Agent name
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
Dict of all available credentials
|
|
266
|
+
"""
|
|
267
|
+
# Start with stored credentials
|
|
268
|
+
creds = dict(self._load_credentials(agent))
|
|
269
|
+
|
|
270
|
+
# Override with environment variables (they take precedence)
|
|
271
|
+
for name in creds:
|
|
272
|
+
if name in os.environ:
|
|
273
|
+
creds[name] = os.environ[name]
|
|
274
|
+
|
|
275
|
+
return creds
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Delegation Manager for agent-to-agent task delegation.
|
|
3
|
+
|
|
4
|
+
Enables agents to invoke other agents (delegates) to perform subtasks,
|
|
5
|
+
supporting multi-agent orchestration patterns.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
from typing import TYPE_CHECKING, Any
|
|
12
|
+
|
|
13
|
+
from supyagent.core.context import DelegationContext, summarize_conversation
|
|
14
|
+
from supyagent.core.executor import ExecutionRunner
|
|
15
|
+
from supyagent.core.registry import AgentRegistry
|
|
16
|
+
from supyagent.models.agent_config import AgentNotFoundError, load_agent_config
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from supyagent.core.agent import Agent
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class DelegationManager:
|
|
23
|
+
"""
|
|
24
|
+
Manages agent-to-agent delegation.
|
|
25
|
+
|
|
26
|
+
Provides tools that allow a parent agent to delegate tasks to
|
|
27
|
+
child agents (delegates), with proper context passing.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
registry: AgentRegistry,
|
|
33
|
+
parent_agent: "Agent",
|
|
34
|
+
grandparent_instance_id: str | None = None,
|
|
35
|
+
):
|
|
36
|
+
"""
|
|
37
|
+
Initialize the delegation manager.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
registry: Agent registry for tracking instances
|
|
41
|
+
parent_agent: The parent agent that will do the delegating
|
|
42
|
+
grandparent_instance_id: Instance ID of the agent that created this one (if any)
|
|
43
|
+
"""
|
|
44
|
+
self.registry = registry
|
|
45
|
+
self.parent = parent_agent
|
|
46
|
+
|
|
47
|
+
# Always register this agent, optionally with a grandparent
|
|
48
|
+
self.parent_id = registry.register(parent_agent, parent_id=grandparent_instance_id)
|
|
49
|
+
|
|
50
|
+
def get_delegation_tools(self) -> list[dict[str, Any]]:
|
|
51
|
+
"""
|
|
52
|
+
Generate tool schemas for each delegatable agent.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
List of OpenAI-format tool definitions
|
|
56
|
+
"""
|
|
57
|
+
tools: list[dict[str, Any]] = []
|
|
58
|
+
|
|
59
|
+
# Get delegate configurations
|
|
60
|
+
for delegate_name in self.parent.config.delegates:
|
|
61
|
+
try:
|
|
62
|
+
delegate_config = load_agent_config(delegate_name)
|
|
63
|
+
except AgentNotFoundError:
|
|
64
|
+
continue
|
|
65
|
+
|
|
66
|
+
tool = {
|
|
67
|
+
"type": "function",
|
|
68
|
+
"function": {
|
|
69
|
+
"name": f"delegate_to_{delegate_name}",
|
|
70
|
+
"description": (
|
|
71
|
+
f"Delegate a task to the {delegate_name} agent. "
|
|
72
|
+
f"{delegate_config.description}"
|
|
73
|
+
),
|
|
74
|
+
"parameters": {
|
|
75
|
+
"type": "object",
|
|
76
|
+
"properties": {
|
|
77
|
+
"task": {
|
|
78
|
+
"type": "string",
|
|
79
|
+
"description": "The task to delegate to this agent",
|
|
80
|
+
},
|
|
81
|
+
"context": {
|
|
82
|
+
"type": "string",
|
|
83
|
+
"description": (
|
|
84
|
+
"Optional context from the current conversation "
|
|
85
|
+
"to pass along"
|
|
86
|
+
),
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
"required": ["task"],
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
}
|
|
93
|
+
tools.append(tool)
|
|
94
|
+
|
|
95
|
+
# Add generic spawn tool
|
|
96
|
+
if self.parent.config.delegates:
|
|
97
|
+
tools.append({
|
|
98
|
+
"type": "function",
|
|
99
|
+
"function": {
|
|
100
|
+
"name": "spawn_agent",
|
|
101
|
+
"description": (
|
|
102
|
+
"Create and run a new agent instance for a specific task. "
|
|
103
|
+
f"Available agents: {', '.join(self.parent.config.delegates)}"
|
|
104
|
+
),
|
|
105
|
+
"parameters": {
|
|
106
|
+
"type": "object",
|
|
107
|
+
"properties": {
|
|
108
|
+
"agent_type": {
|
|
109
|
+
"type": "string",
|
|
110
|
+
"description": "The type of agent to spawn",
|
|
111
|
+
"enum": self.parent.config.delegates,
|
|
112
|
+
},
|
|
113
|
+
"task": {
|
|
114
|
+
"type": "string",
|
|
115
|
+
"description": "The task for the agent to perform",
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
"required": ["agent_type", "task"],
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
return tools
|
|
124
|
+
|
|
125
|
+
def is_delegation_tool(self, tool_name: str) -> bool:
|
|
126
|
+
"""Check if a tool name is a delegation tool."""
|
|
127
|
+
return (
|
|
128
|
+
tool_name.startswith("delegate_to_")
|
|
129
|
+
or tool_name == "spawn_agent"
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
def execute_delegation(self, tool_call: Any) -> dict[str, Any]:
|
|
133
|
+
"""
|
|
134
|
+
Execute a delegation tool call.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
tool_call: The tool call from the LLM
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
Result dict with ok/data or ok/error
|
|
141
|
+
"""
|
|
142
|
+
name = tool_call.function.name
|
|
143
|
+
|
|
144
|
+
try:
|
|
145
|
+
args = json.loads(tool_call.function.arguments)
|
|
146
|
+
except json.JSONDecodeError:
|
|
147
|
+
return {"ok": False, "error": "Invalid JSON in tool arguments"}
|
|
148
|
+
|
|
149
|
+
if name == "spawn_agent":
|
|
150
|
+
return self._spawn_agent(
|
|
151
|
+
args.get("agent_type", ""),
|
|
152
|
+
args.get("task", ""),
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
if name.startswith("delegate_to_"):
|
|
156
|
+
agent_name = name[len("delegate_to_"):]
|
|
157
|
+
return self._delegate_task(
|
|
158
|
+
agent_name,
|
|
159
|
+
args.get("task", ""),
|
|
160
|
+
args.get("context"),
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
return {"ok": False, "error": f"Unknown delegation tool: {name}"}
|
|
164
|
+
|
|
165
|
+
def _build_context(
|
|
166
|
+
self,
|
|
167
|
+
task: str,
|
|
168
|
+
extra_context: str | None = None,
|
|
169
|
+
) -> DelegationContext:
|
|
170
|
+
"""Build context to pass to a delegate."""
|
|
171
|
+
# Get conversation summary if we have messages
|
|
172
|
+
summary = None
|
|
173
|
+
if hasattr(self.parent, "messages") and self.parent.messages:
|
|
174
|
+
summary = summarize_conversation(
|
|
175
|
+
self.parent.messages,
|
|
176
|
+
self.parent.llm,
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
context = DelegationContext(
|
|
180
|
+
parent_agent=self.parent.config.name,
|
|
181
|
+
parent_task=task,
|
|
182
|
+
conversation_summary=summary,
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
if extra_context:
|
|
186
|
+
context.relevant_facts.append(extra_context)
|
|
187
|
+
|
|
188
|
+
return context
|
|
189
|
+
|
|
190
|
+
def _delegate_task(
|
|
191
|
+
self,
|
|
192
|
+
agent_name: str,
|
|
193
|
+
task: str,
|
|
194
|
+
extra_context: str | None = None,
|
|
195
|
+
) -> dict[str, Any]:
|
|
196
|
+
"""
|
|
197
|
+
Delegate a task to another agent.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
agent_name: Name of the agent to delegate to
|
|
201
|
+
task: The task to perform
|
|
202
|
+
extra_context: Optional additional context
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
Result dict
|
|
206
|
+
"""
|
|
207
|
+
# Verify it's in the delegates list
|
|
208
|
+
if agent_name not in self.parent.config.delegates:
|
|
209
|
+
return {
|
|
210
|
+
"ok": False,
|
|
211
|
+
"error": f"Agent '{agent_name}' is not in the delegates list",
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
# Load the delegate config
|
|
215
|
+
try:
|
|
216
|
+
config = load_agent_config(agent_name)
|
|
217
|
+
except AgentNotFoundError:
|
|
218
|
+
return {"ok": False, "error": f"Agent '{agent_name}' not found"}
|
|
219
|
+
|
|
220
|
+
# Build context
|
|
221
|
+
context = self._build_context(task, extra_context)
|
|
222
|
+
full_task = f"{context.to_prompt()}\n\n---\n\nYour task:\n{task}"
|
|
223
|
+
|
|
224
|
+
# Check delegation depth
|
|
225
|
+
parent_depth = self.registry.get_depth(self.parent_id)
|
|
226
|
+
if parent_depth >= AgentRegistry.MAX_DEPTH:
|
|
227
|
+
return {
|
|
228
|
+
"ok": False,
|
|
229
|
+
"error": (
|
|
230
|
+
f"Maximum delegation depth ({AgentRegistry.MAX_DEPTH}) reached. "
|
|
231
|
+
"Cannot delegate further."
|
|
232
|
+
),
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
try:
|
|
236
|
+
if config.type == "execution":
|
|
237
|
+
# Use execution runner for execution agents
|
|
238
|
+
runner = ExecutionRunner(config)
|
|
239
|
+
result = runner.run(full_task, output_format="json")
|
|
240
|
+
else:
|
|
241
|
+
# For interactive agents, create a new instance
|
|
242
|
+
from supyagent.core.agent import Agent
|
|
243
|
+
|
|
244
|
+
sub_agent = Agent(
|
|
245
|
+
config,
|
|
246
|
+
registry=self.registry,
|
|
247
|
+
parent_instance_id=self.parent_id,
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
response = sub_agent.send_message(full_task)
|
|
251
|
+
result = {"ok": True, "data": response}
|
|
252
|
+
|
|
253
|
+
# Mark as completed
|
|
254
|
+
if sub_agent.instance_id:
|
|
255
|
+
self.registry.mark_completed(sub_agent.instance_id)
|
|
256
|
+
|
|
257
|
+
return result
|
|
258
|
+
|
|
259
|
+
except Exception as e:
|
|
260
|
+
return {"ok": False, "error": f"Delegation failed: {str(e)}"}
|
|
261
|
+
|
|
262
|
+
def _spawn_agent(
|
|
263
|
+
self,
|
|
264
|
+
agent_type: str,
|
|
265
|
+
task: str,
|
|
266
|
+
) -> dict[str, Any]:
|
|
267
|
+
"""
|
|
268
|
+
Spawn a new agent instance.
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
agent_type: Type of agent to spawn
|
|
272
|
+
task: Initial task for the agent
|
|
273
|
+
|
|
274
|
+
Returns:
|
|
275
|
+
Result dict
|
|
276
|
+
"""
|
|
277
|
+
if agent_type not in self.parent.config.delegates:
|
|
278
|
+
return {
|
|
279
|
+
"ok": False,
|
|
280
|
+
"error": (
|
|
281
|
+
f"Cannot spawn '{agent_type}' - not in delegates list. "
|
|
282
|
+
f"Available: {', '.join(self.parent.config.delegates)}"
|
|
283
|
+
),
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return self._delegate_task(agent_type, task)
|