Skip to content
2 changes: 2 additions & 0 deletions src/praisonai-agents/praisonaiagents/managed/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
ComputeConfig,
InstanceInfo,
InstanceStatus,
ManagedBackendProtocol,
)

__all__ = [
Expand All @@ -41,4 +42,5 @@
"ComputeConfig",
"InstanceInfo",
"InstanceStatus",
"ManagedBackendProtocol",
]
128 changes: 128 additions & 0 deletions src/praisonai-agents/praisonaiagents/managed/protocols.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,3 +216,131 @@ async def list_instances(self) -> List[InstanceInfo]:
List of InstanceInfo for running instances.
"""
...


@runtime_checkable
class ManagedBackendProtocol(Protocol):
"""Protocol for external managed agent backends.

Defines the contract between PraisonAI Agent's delegation layer
and any managed agent infrastructure provider (Anthropic Managed Agents, etc.).

The Core SDK defines *what* — this protocol.
The Wrapper implements *how* — the provider-specific adapter.

Lifecycle::

backend = SomeManagedBackend(config={...})
agent = Agent(name="coder", backend=backend)
result = agent.start("Write a script") # delegates to backend.execute()

Implementations must handle:
- Agent/environment/session creation and caching
- Event streaming (agent.message, agent.tool_use, session.status_idle)
- Custom tool calls (agent.custom_tool_use → user.custom_tool_result)
- Tool confirmation (always_ask policy → user.tool_confirmation)
- Usage tracking (input_tokens, output_tokens)
- Session reset for multi-turn isolation
"""

async def execute(self, prompt: str, **kwargs) -> str:
"""Execute a prompt on managed infrastructure and return the full response.

This is the primary entry point called by Agent._delegate_to_backend().

Args:
prompt: The user message to send to the managed agent.
**kwargs: Provider-specific options (e.g., timeout, metadata).

Returns:
The agent's complete text response.
"""
...

async def stream(self, prompt: str, **kwargs):
"""Stream a prompt response as text chunks.

Yields text fragments as the managed agent produces them.
Used when Agent is invoked with stream=True.

Args:
prompt: The user message.
**kwargs: Provider-specific options.

Yields:
Text chunks from the agent's response.
"""
...
yield "" # type: ignore[misc]

def reset_session(self) -> None:
"""Discard the cached session so the next execute() creates a fresh one.

The agent and environment remain cached for reuse.
"""
...

def reset_all(self) -> None:
"""Discard all cached state (agent, environment, session, client).

Next execute() call will re-create everything from scratch.
"""
...

# ── Optional methods (default no-ops for backward compat) ──

def update_agent(self, **kwargs) -> None:
"""Update an existing managed agent's configuration.

Allows changing system prompt, tools, model, etc. on a previously
created agent without recreating it.

Args:
**kwargs: Fields to update (system, tools, model, name, etc.).
"""
...

def interrupt(self) -> None:
"""Send a user interrupt to the active session.

Signals the managed agent to stop its current work (equivalent to
``user.interrupt`` event in the Anthropic API).
"""
...

def retrieve_session(self) -> Dict[str, Any]:
"""Retrieve the current managed session's metadata and usage.

Standardized return schema::

{
"session_id": "sesn_01234",
"agent_id": "agent_01234",
"environment_id": "env_01234",
"status": "idle" | "running" | "error",
"created_at": 1234567890.0,
"last_activity_at": 1234567890.0,
"usage": {
"input_tokens": 150,
"output_tokens": 75,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 0
},
"metadata": {}
}

Returns:
Dict with standardized session info.
"""
...

def list_sessions(self, **kwargs) -> List[Dict[str, Any]]:
"""List sessions for the current agent.

Args:
**kwargs: Provider-specific filters (limit, status, etc.).

Returns:
List of session summary dicts with same schema as retrieve_session().
"""
...
91 changes: 91 additions & 0 deletions src/praisonai/praisonai/cli/commands/managed.py
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,97 @@ def ids_restore(
print(result)


# ─────────────────────────────────────────────────────────────────────────────
# Delete commands for missing CLI operations (Fix A7)
# ─────────────────────────────────────────────────────────────────────────────

@sessions_app.command("delete")
def sessions_delete(
session_id: str = typer.Argument(..., help="Session ID to delete"),
force: bool = typer.Option(False, "--force", help="Force delete without confirmation"),
):
"""Delete a managed session."""
if not force:
confirm = typer.confirm(f"Delete session {session_id}?")
if not confirm:
typer.echo("Cancelled.")
return

client = _get_client()
try:
# For Anthropic managed agents, delete via API
response = client.delete(f"/v1/agent_sessions/{session_id}")
typer.secho(f"Session {session_id} deleted", fg="green")
except Exception as e:
typer.secho(f"Failed to delete session: {e}", fg="red")


@agents_app.command("delete")
def agents_delete(
agent_id: str = typer.Argument(..., help="Agent ID to delete"),
force: bool = typer.Option(False, "--force", help="Force delete without confirmation"),
):
"""Delete a managed agent."""
if not force:
confirm = typer.confirm(f"Delete agent {agent_id}? This will also delete all its sessions.")
if not confirm:
typer.echo("Cancelled.")
return

client = _get_client()
try:
response = client.delete(f"/v1/agents/{agent_id}")
typer.secho(f"Agent {agent_id} deleted", fg="green")
except Exception as e:
typer.secho(f"Failed to delete agent: {e}", fg="red")


@envs_app.command("update")
def envs_update(
env_id: str = typer.Argument(..., help="Environment ID to update"),
name: Optional[str] = typer.Option(None, "--name", help="New environment name"),
description: Optional[str] = typer.Option(None, "--description", help="New environment description"),
):
"""Update a managed environment."""
if not name and not description:
typer.echo("Must specify at least --name or --description")
return

client = _get_client()
try:
data = {}
if name:
data["name"] = name
if description:
data["description"] = description

response = client.patch(f"/v1/environments/{env_id}", json=data)
typer.secho(f"Environment {env_id} updated", fg="green")
typer.echo(json.dumps(response.json(), indent=2))
except Exception as e:
typer.secho(f"Failed to update environment: {e}", fg="red")
Comment on lines +551 to +607
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm which beta resource namespaces and delete/update methods the installed
# anthropic SDK actually exposes, and confirm the installed version.
rg -nP --type=py -g '**/anthropic/**' '\b(def\s+(delete|update)\b|class\s+\w*(Sessions|Agents|Environments)\w*\b)' || true
fd -t f 'pyproject.toml|setup.cfg|setup.py|requirements*.txt' | xargs rg -n -C1 '\banthropic\b' || true

Repository: MervinPraison/PraisonAI

Length of output: 49


🌐 Web query:

Which methods does the Anthropic Python SDK expose on client.beta.sessions, client.beta.agents, and client.beta.environments for delete and update operations?

💡 Result:

client.beta.sessions exposes: - delete(session_id): Deletes a session. Returns BetaManagedAgentsDeletedSession. - update(session_id, kwargs): Updates session metadata/title. Returns BetaManagedAgentsSession. client.beta.agents exposes: - update(agent_id, kwargs): Updates agent configuration. Returns BetaManagedAgentsAgent. - No delete method (uses archive instead). client.beta.environments exposes: - delete(environment_id): Deletes an environment. Returns BetaEnvironmentDeleteResponse. - update(environment_id, kwargs): Updates environment configuration. Returns BetaEnvironment.

Citations:


🌐 Web query:

What is the correct method to delete or archive a managed agent in the Anthropic Python SDK? Does client.beta.agents have a delete() method or does it use archive()?

💡 Result:

The correct method to manage (delete or archive) a managed agent in the Anthropic Python SDK is to use client.beta.agents.archive(agent_id). There is no delete method for agents; agents can only be archived, not hard-deleted. Archiving makes the agent read-only: new sessions cannot reference it, but existing sessions continue running. Example: client.beta.agents.archive(agent_id="agent_123") This is confirmed across official Anthropic documentation and SDK references. Other resources like sessions, environments, and vaults support both delete and archive, but agents specifically do not have delete.

Citations:


Replace raw HTTP calls with SDK resource methods — agents use archive, not delete.

All other commands here route through the Anthropic SDK's resource-oriented namespace (e.g. client.beta.sessions.retrieve, client.beta.agents.update, client.beta.environments.retrieve). The new delete/update commands instead call client.delete("/v1/...") and client.patch("/v1/...") directly, which:

  1. Bypass the typed beta resource methods (client.beta.sessions.delete, client.beta.agents.archive, client.beta.environments.delete, client.beta.environments.update) that are the documented surface for these operations and consistent with the rest of this file.
  2. Assume an httpx-like return shape. client.patch(...) in the Anthropic SDK returns a parsed/typed pydantic object, not an httpx.Response, so response.json() at line 601 returns a JSON string (pydantic's serializer), which then gets double-encoded by json.dumps(...) on the same line.
  3. Hard-code /v1/... URL paths that may not match the beta API routes the rest of the CLI targets.
  4. Don't set non-zero exit codes on failure, breaking scripting and automation.

Critical: agents do not support delete — use client.beta.agents.archive(agent_id) instead. Replace with:

  • sessions_delete: client.beta.sessions.delete(session_id) (with raise typer.Exit(1) on exception)
  • agents_delete: client.beta.agents.archive(agent_id) (with raise typer.Exit(1) on exception)
  • envs_update: client.beta.environments.update(env_id, name=..., description=...) and output the typed response fields directly (not .json())
  • envs_delete: client.beta.environments.delete(env_id) (with raise typer.Exit(1) on exception)
🧰 Tools
🪛 Ruff (0.15.10)

[warning] 556-556: Do not catch blind exception: Exception

(BLE001)


[warning] 576-576: Do not catch blind exception: Exception

(BLE001)


[warning] 602-602: Do not catch blind exception: Exception

(BLE001)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/praisonai/praisonai/cli/commands/managed.py` around lines 551 - 603, The
raw HTTP calls should be replaced with the SDK resource methods and failures
should exit non-zero: use client.beta.sessions.delete(session_id) in
sessions_delete, client.beta.agents.archive(agent_id) in agents_delete (agents
do not support delete), client.beta.environments.update(env_id, name=...,
description=...) in envs_update and client.beta.environments.delete(env_id) for
envs_delete; remove any .json()/json.dumps usage (use the returned typed object
fields or str(obj)) and on exceptions call typer.secho(..., fg="red") then raise
typer.Exit(1) to set a non-zero exit code.



@envs_app.command("delete")
def envs_delete(
env_id: str = typer.Argument(..., help="Environment ID to delete"),
force: bool = typer.Option(False, "--force", help="Force delete without confirmation"),
):
"""Delete a managed environment."""
if not force:
confirm = typer.confirm(f"Delete environment {env_id}? This may affect running agents.")
if not confirm:
typer.echo("Cancelled.")
return

client = _get_client()
try:
response = client.delete(f"/v1/environments/{env_id}")
typer.secho(f"Environment {env_id} deleted", fg="green")
except Exception as e:
typer.secho(f"Failed to delete environment: {e}", fg="red")
Comment on lines +556 to +629
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Failures are swallowed — CLI returns exit 0 even when the server rejects the request.

Each except Exception branch (lines 556-557, 576-577, 602-603, 622-623) prints a red message and falls through, so scripts calling praisonai managed … delete/update cannot detect failures via exit code. Add raise typer.Exit(1) after the error message (applied in the diff above). Ruff also flags the blind except Exception (BLE001); since the SDK raises typed anthropic.APIError/APIStatusError, consider narrowing to at least anthropic.APIError to avoid masking programming bugs like AttributeError.

🧰 Tools
🪛 Ruff (0.15.10)

[warning] 556-556: Do not catch blind exception: Exception

(BLE001)


[warning] 576-576: Do not catch blind exception: Exception

(BLE001)


[warning] 602-602: Do not catch blind exception: Exception

(BLE001)


[warning] 622-622: Do not catch blind exception: Exception

(BLE001)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/praisonai/praisonai/cli/commands/managed.py` around lines 556 - 623, The
except branches in the delete/update CLI handlers (the session delete block,
agents_delete, envs_update, and envs_delete) currently catch Exception, print an
error and continue, which swallows failures and returns exit 0; change each
except to catch the SDK error type (e.g., anthropic.APIError or APIStatusError)
instead of bare Exception, and after calling typer.secho(...) add raise
typer.Exit(1) so the command exits with non‑zero on server errors; update the
except clauses in agents_delete, envs_update, envs_delete (and the session
delete block) to reflect these changes.



# ─────────────────────────────────────────────────────────────────────────────
# Register sub-apps onto main app
# ─────────────────────────────────────────────────────────────────────────────
Expand Down
Loading
Loading