Teach builder to author DeepAgents skills
All checks were successful
build / build (push) Successful in 26s
All checks were successful
build / build (push) Successful in 26s
This commit is contained in:
149
agent_builder/skills/a2apack-agent-authoring/SKILL.md
Normal file
149
agent_builder/skills/a2apack-agent-authoring/SKILL.md
Normal file
@@ -0,0 +1,149 @@
|
||||
---
|
||||
name: a2apack-agent-authoring
|
||||
description: Author production a2a-pack agents with clear public @skill schemas, runtime declarations, caller-provided LLMs, workspace grants, pricing, resources, and secure platform capabilities. Use when writing or reviewing agent.py for an A2A agent.
|
||||
---
|
||||
# A2A Pack Agent Authoring
|
||||
|
||||
An A2A agent should expose a small, stable public API and put rich reasoning
|
||||
inside implementation helpers, DeepAgents skills, or subagents. The public
|
||||
`@skill` methods are the contract shown on the Agent Card.
|
||||
|
||||
## Public Contract
|
||||
|
||||
Use `A2AAgent` class attributes for runtime and marketplace behavior:
|
||||
|
||||
- `name`, `description`, and `version` identify the agent.
|
||||
- `config_model` and `auth_model` define agent configuration and caller auth.
|
||||
- `llm_provisioning` tells the platform where LLM credentials come from.
|
||||
- `pricing` tells callers what they pay and whether they supply LLM creds.
|
||||
- `resources`, `egress`, `tools_used`, and `workspace_access` describe runtime
|
||||
requirements.
|
||||
- `wants_cp_jwt=True` is only for trusted platform agents. It forwards the
|
||||
caller's control-plane token.
|
||||
|
||||
Keep each `@skill` method `async`, put `RunContext[...]` immediately after
|
||||
`self`, and annotate every public argument. Do not use `*args` or `**kwargs`;
|
||||
they are rejected and would not publish a useful schema.
|
||||
|
||||
## Code Example
|
||||
|
||||
```python
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from a2a_pack import (
|
||||
A2AAgent,
|
||||
EgressPolicy,
|
||||
LLMProvisioning,
|
||||
NoAuth,
|
||||
Pricing,
|
||||
Resources,
|
||||
RunContext,
|
||||
WorkspaceAccess,
|
||||
WorkspaceMode,
|
||||
skill,
|
||||
)
|
||||
|
||||
|
||||
class ResearchConfig(BaseModel):
|
||||
default_depth: int = 3
|
||||
|
||||
|
||||
class ResearchAgent(A2AAgent[ResearchConfig, NoAuth]):
|
||||
name = "research-agent"
|
||||
description = "Researches a topic and writes a concise cited report."
|
||||
version = "0.1.0"
|
||||
|
||||
config_model = ResearchConfig
|
||||
auth_model = NoAuth
|
||||
|
||||
llm_provisioning = LLMProvisioning.CALLER_PROVIDED
|
||||
pricing = Pricing(
|
||||
price_per_call_usd=0.05,
|
||||
caller_pays_llm=True,
|
||||
notes="Caller supplies LLM credentials through the platform.",
|
||||
)
|
||||
resources = Resources(cpu="1", memory="1Gi", max_runtime_seconds=900)
|
||||
egress = EgressPolicy(allow_hosts=("api.openai.com",))
|
||||
tools_used = ("deepagents", "langchain")
|
||||
workspace_access = WorkspaceAccess.dynamic(
|
||||
max_files=128,
|
||||
allowed_modes=(WorkspaceMode.READ_ONLY, WorkspaceMode.READ_WRITE_OVERLAY),
|
||||
require_reason=False,
|
||||
)
|
||||
|
||||
@skill(
|
||||
description="Research a topic, save report artifacts, and return a summary",
|
||||
timeout_seconds=900,
|
||||
cost_class="llm-heavy",
|
||||
)
|
||||
async def research(
|
||||
self,
|
||||
ctx: RunContext[NoAuth],
|
||||
topic: str,
|
||||
depth: int | None = None,
|
||||
save_path: str = "outputs/research-report.md",
|
||||
) -> dict[str, Any]:
|
||||
creds = ctx.llm
|
||||
await ctx.emit_progress(f"researching with {creds.model}")
|
||||
graph = self._build_graph(ctx)
|
||||
state = await graph.ainvoke(
|
||||
{
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": (
|
||||
f"Research {topic!r} at depth {depth or self.config.default_depth}. "
|
||||
f"Write the final markdown report to /workspace/{save_path}."
|
||||
),
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
report_text = _last_message_text(state)
|
||||
ref = await ctx.write_artifact(
|
||||
"research-summary.md",
|
||||
report_text.encode("utf-8"),
|
||||
"text/markdown",
|
||||
)
|
||||
await ctx.emit_artifact(ref)
|
||||
return {"summary": report_text, "artifact": ref.uri, "path": save_path}
|
||||
```
|
||||
|
||||
## Schema Rules
|
||||
|
||||
Public arguments become the live A2A input schema. Prefer plain JSON-shaped
|
||||
types: `str`, `int`, `float`, `bool`, `list[str]`, `dict[str, Any]`, or
|
||||
Pydantic models. Provide defaults for optional knobs. Avoid exposing internal
|
||||
implementation details such as model names, temporary paths, prompt fragments,
|
||||
or unbounded command strings.
|
||||
|
||||
## Runtime Rules
|
||||
|
||||
If a skill calls `ctx.workspace_backend()` or `ctx.workspace`, declare
|
||||
`workspace_access`. If it writes user-visible files, also emit artifacts with
|
||||
`ctx.write_artifact` and `ctx.emit_artifact`.
|
||||
|
||||
If an agent needs system binaries, mirror the need in `a2a.yaml`:
|
||||
|
||||
```yaml
|
||||
runtime:
|
||||
apt_packages: [ffmpeg, poppler-utils]
|
||||
resources:
|
||||
cpu: "2"
|
||||
memory: 2Gi
|
||||
max_runtime_seconds: 900
|
||||
```
|
||||
|
||||
If it calls external hosts, declare `egress` on the class. If it needs secrets,
|
||||
use `required_secrets` and `ctx.secret("NAME")`; do not hard-code credentials.
|
||||
|
||||
## A2A Skill vs DeepAgents Skill
|
||||
|
||||
Use A2A `@skill` for caller-visible operations and billing boundaries. Use
|
||||
DeepAgents skills for internal procedural knowledge the LLM should apply. A
|
||||
generated project can have one public `@skill` that invokes a DeepAgent loaded
|
||||
with several internal `skills/<name>/SKILL.md` bundles.
|
||||
80
agent_builder/skills/agent-quality-review/SKILL.md
Normal file
80
agent_builder/skills/agent-quality-review/SKILL.md
Normal file
@@ -0,0 +1,80 @@
|
||||
---
|
||||
name: agent-quality-review
|
||||
description: Review a generated a2a-pack agent before sandbox testing or deploy. Use to catch broken cards, missing schemas, fake tools, unwired DeepAgents skills, missing runtime mirrors, unsafe IO, and stale live Agent Card schemas.
|
||||
---
|
||||
# Agent Quality Review
|
||||
|
||||
Before `test_agent_in_sandbox` and before deploy, review the generated project
|
||||
as source code, not just as text.
|
||||
|
||||
## Required Files
|
||||
|
||||
Every generated project needs:
|
||||
|
||||
- `agent.py`
|
||||
- `a2a.yaml`
|
||||
- `requirements.txt`
|
||||
- Optional `skills/<skill-name>/SKILL.md` bundles for nontrivial DeepAgents
|
||||
behavior
|
||||
|
||||
Run `list_agent_files` and read the files that matter. If the project was
|
||||
deployed before, use `sync_agent_workspace_from_repo` before editing when the
|
||||
builder needs to continue from the current managed repo source.
|
||||
|
||||
## Agent.py Review
|
||||
|
||||
Check these items:
|
||||
|
||||
- Public `@skill` methods are async and have `RunContext[...]` plus typed JSON
|
||||
arguments.
|
||||
- The public schema is small and stable. No unbounded command strings, hidden
|
||||
prompt fragments, or provider credentials as public inputs.
|
||||
- The implementation reads `ctx.llm` when `llm_provisioning` is
|
||||
`CALLER_PROVIDED`.
|
||||
- Any use of DeepAgents file tools passes `backend=ctx.workspace_backend()`.
|
||||
- Any project skills are seeded into the backend and passed with
|
||||
`skills=skill_sources or None`.
|
||||
- Custom subagents that need project skills include their own `skills` field.
|
||||
- Deterministic tools do exact work; no fake canned-response tools.
|
||||
- File-producing skills call `ctx.write_artifact` and `ctx.emit_artifact`.
|
||||
- External calls, secrets, workspace access, pricing, and resources are
|
||||
declared on the class.
|
||||
|
||||
## A2A YAML Review
|
||||
|
||||
Mirror deployment-only details in `a2a.yaml` because the platform reads it
|
||||
before importing user code:
|
||||
|
||||
```yaml
|
||||
name: research-agent
|
||||
version: 0.1.0
|
||||
entrypoint: agent:ResearchAgent
|
||||
expose:
|
||||
public: true
|
||||
runtime:
|
||||
apt_packages: [ffmpeg]
|
||||
resources:
|
||||
cpu: "2"
|
||||
memory: 2Gi
|
||||
max_runtime_seconds: 900
|
||||
```
|
||||
|
||||
Use `runtime.apt_packages` only for Debian system binaries. Python packages
|
||||
belong in `requirements.txt`.
|
||||
|
||||
## Sandbox And Live Card
|
||||
|
||||
Always run `test_agent_in_sandbox(name)` before deploy. Treat nonzero exit as
|
||||
source failure, then read stderr, edit, and rerun.
|
||||
|
||||
After `cp_deploy_tarball`, compare `live_skills[].input_schema` with the
|
||||
actual public `@skill` signatures. If a live schema is missing an argument or
|
||||
shows an old shape, refresh or redeploy until the live Agent Card matches the
|
||||
source.
|
||||
|
||||
## Minimal Good Result
|
||||
|
||||
For most generated agents, a good result is one public A2A `@skill`, one
|
||||
workspace-backed DeepAgent, one or two internal DeepAgents skills, and a few
|
||||
small deterministic tools. Resist broad endpoint sets unless the user asked
|
||||
for a multi-operation agent.
|
||||
117
agent_builder/skills/deepagent-agent-design/SKILL.md
Normal file
117
agent_builder/skills/deepagent-agent-design/SKILL.md
Normal file
@@ -0,0 +1,117 @@
|
||||
---
|
||||
name: deepagent-agent-design
|
||||
description: Design generated A2A agents around DeepAgents skills, subagents, durable workspace backends, and caller-provided LLM reasoning. Use whenever building or modifying an agent, deciding whether to add tools, creating skill bundles, wiring create_deep_agent, or avoiding shallow fake tools.
|
||||
---
|
||||
# DeepAgent Agent Design
|
||||
|
||||
Build generated agents as small A2A surfaces around a capable inner DeepAgent.
|
||||
The A2A `@skill` is the user-facing API entrypoint. The inner DeepAgent does
|
||||
planning, file work, skill selection, and subagent delegation.
|
||||
|
||||
## Default Architecture
|
||||
|
||||
Use this shape for non-trivial generated agents:
|
||||
|
||||
1. Keep one or two public A2A `@skill` methods with clear typed parameters.
|
||||
2. Inside each method, read `ctx.llm`, construct `ChatOpenAI`, and build a
|
||||
DeepAgent with `create_deep_agent`.
|
||||
3. Pass `backend=ctx.workspace_backend()` so DeepAgents file tools write to the
|
||||
caller's durable workspace instead of LangGraph state.
|
||||
4. Pass `skills=[RUNTIME_SKILLS_ROOT]` when the generated project includes
|
||||
`skills/<skill-name>/SKILL.md`.
|
||||
5. For custom subagents, include a `skills` field on each subagent definition.
|
||||
The general-purpose subagent inherits main skills, but custom subagents do
|
||||
not.
|
||||
|
||||
DeepAgents skills are progressive-disclosure folders:
|
||||
|
||||
```text
|
||||
skills/
|
||||
skill-name/
|
||||
SKILL.md
|
||||
references/...
|
||||
scripts/...
|
||||
assets/...
|
||||
```
|
||||
|
||||
`SKILL.md` must start with YAML frontmatter containing `name` and
|
||||
`description`. The description is the trigger; include exactly when the skill
|
||||
should be used. Keep detailed reference material in separate files that are
|
||||
linked from `SKILL.md`.
|
||||
|
||||
## Runtime Skill Seeding
|
||||
|
||||
Project `skills/` files are source files in the agent image. DeepAgents loads
|
||||
skills from its backend, so seed those files into the invocation workspace
|
||||
before `create_deep_agent`:
|
||||
|
||||
```python
|
||||
from pathlib import Path
|
||||
|
||||
RUNTIME_SKILLS_ROOT = "/outputs/{{ agent_name }}/.deepagents/skills/"
|
||||
|
||||
|
||||
def _seed_runtime_skills(backend: Any) -> None:
|
||||
root = Path(__file__).parent / "skills"
|
||||
if not root.exists():
|
||||
return
|
||||
uploads: list[tuple[str, bytes]] = []
|
||||
for path in root.rglob("*"):
|
||||
if path.is_file():
|
||||
rel = path.relative_to(root).as_posix()
|
||||
uploads.append((RUNTIME_SKILLS_ROOT + rel, path.read_bytes()))
|
||||
if uploads:
|
||||
backend.upload_files(uploads)
|
||||
```
|
||||
|
||||
Then:
|
||||
|
||||
```python
|
||||
backend = ctx.workspace_backend()
|
||||
_seed_runtime_skills(backend)
|
||||
return create_deep_agent(
|
||||
model=model,
|
||||
backend=backend,
|
||||
skills=[RUNTIME_SKILLS_ROOT],
|
||||
tools=[small_deterministic_tool],
|
||||
subagents=[...],
|
||||
system_prompt=SYSTEM_PROMPT,
|
||||
)
|
||||
```
|
||||
|
||||
Because this uses `/outputs/...`, the write fits the normal A2A workspace
|
||||
grant. If the agent uses `ctx.workspace_backend()`, also declare
|
||||
`workspace_access = WorkspaceAccess.dynamic(...)` on the A2A agent class.
|
||||
|
||||
## Tool Design Rules
|
||||
|
||||
Do not make a long list of fake tools that just return canned text or call the
|
||||
LLM again. Prefer:
|
||||
|
||||
- DeepAgents skills for procedural/domain knowledge the LLM should apply.
|
||||
- Subagents for independent research, analysis, review, or transformation
|
||||
lanes that benefit from another LLM pass.
|
||||
- Small deterministic tools only for exact operations: parsing, validation,
|
||||
API calls, math, file conversion, sandbox commands, database queries.
|
||||
|
||||
If a tool's body is mostly prompt text, it should usually be a DeepAgents
|
||||
skill instead. If a tool needs judgement, route that judgement through the
|
||||
inner DeepAgent or a subagent, not a hard-coded placeholder.
|
||||
|
||||
## Skill Authoring Workflow
|
||||
|
||||
When the generated agent needs reusable workflow knowledge, first call
|
||||
`write_agent_skill` to create `skills/<skill-name>/SKILL.md` and any support
|
||||
files. Then wire `agent.py` to seed and pass those skills.
|
||||
|
||||
Use concise skill descriptions with concrete trigger phrases. Example:
|
||||
|
||||
```yaml
|
||||
---
|
||||
name: market-research
|
||||
description: Plan and run multi-source market research, delegate subtopics to subagents, save findings files, and synthesize cited reports. Use for competitive analysis, market maps, customer research, or current market questions.
|
||||
---
|
||||
```
|
||||
|
||||
Keep A2A public schemas stable and simple. Put rich autonomous behavior behind
|
||||
the inner DeepAgent, not in a pile of public endpoints.
|
||||
129
agent_builder/skills/deepagents-implementation-patterns/SKILL.md
Normal file
129
agent_builder/skills/deepagents-implementation-patterns/SKILL.md
Normal file
@@ -0,0 +1,129 @@
|
||||
---
|
||||
name: deepagents-implementation-patterns
|
||||
description: Implement DeepAgents inside a2a-pack agents with real deterministic tools, project skills, custom subagents, workspace-backed files, and caller-provided ChatOpenAI models. Use when editing create_deep_agent calls or deciding what belongs in tools, skills, or subagents.
|
||||
---
|
||||
# DeepAgents Implementation Patterns
|
||||
|
||||
DeepAgents supplies the inner agent loop. In this platform, the reliable shape
|
||||
is: caller-provided `ChatOpenAI`, A2A workspace backend, project DeepAgents
|
||||
skills, and a small number of exact tools.
|
||||
|
||||
## Implementation Shape
|
||||
|
||||
```python
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from langchain_core.tools import tool
|
||||
from langchain_openai import ChatOpenAI
|
||||
from deepagents import create_deep_agent
|
||||
|
||||
RUNTIME_SKILLS_ROOT = "/outputs/research-agent/.deepagents/skills/"
|
||||
|
||||
SYSTEM_PROMPT = """\
|
||||
You are a careful research agent. Use project skills for workflow rules and
|
||||
write durable files under /workspace/outputs/.
|
||||
"""
|
||||
|
||||
|
||||
def build_graph(ctx: Any) -> Any:
|
||||
creds = ctx.llm
|
||||
model = ChatOpenAI(
|
||||
model=creds.model,
|
||||
base_url=creds.base_url,
|
||||
api_key=creds.api_key,
|
||||
temperature=0.0,
|
||||
)
|
||||
|
||||
@tool
|
||||
def validate_citations(items_json: str) -> str:
|
||||
"""Validate that each citation has title, url, and claim fields."""
|
||||
items = json.loads(items_json)
|
||||
missing = [
|
||||
i for i, item in enumerate(items)
|
||||
if not {"title", "url", "claim"} <= set(item)
|
||||
]
|
||||
return json.dumps({"ok": not missing, "missing_indexes": missing})
|
||||
|
||||
backend = ctx.workspace_backend()
|
||||
skill_sources = seed_runtime_skills(backend)
|
||||
return create_deep_agent(
|
||||
model=model,
|
||||
backend=backend,
|
||||
skills=skill_sources or None,
|
||||
tools=[validate_citations],
|
||||
subagents=[
|
||||
{
|
||||
"name": "source-reviewer",
|
||||
"description": "Reviews source quality and citation coverage.",
|
||||
"system_prompt": (
|
||||
"Check whether sources support the claims. Return gaps "
|
||||
"and concrete fixes."
|
||||
),
|
||||
"skills": skill_sources,
|
||||
}
|
||||
],
|
||||
system_prompt=SYSTEM_PROMPT,
|
||||
)
|
||||
|
||||
|
||||
def seed_runtime_skills(backend: Any) -> list[str]:
|
||||
root = Path(__file__).parent / "skills"
|
||||
if not root.exists():
|
||||
return []
|
||||
uploads: list[tuple[str, bytes]] = []
|
||||
for path in root.rglob("*"):
|
||||
if path.is_file():
|
||||
rel = path.relative_to(root).as_posix()
|
||||
uploads.append((RUNTIME_SKILLS_ROOT + rel, path.read_bytes()))
|
||||
if uploads:
|
||||
backend.upload_files(uploads)
|
||||
return [RUNTIME_SKILLS_ROOT]
|
||||
return []
|
||||
```
|
||||
|
||||
## Choose The Right Primitive
|
||||
|
||||
Use a DeepAgents skill when the agent needs reusable judgement, process, domain
|
||||
rules, examples, or a checklist. Put it under `skills/<skill-name>/SKILL.md`.
|
||||
|
||||
Use a subagent when a separate LLM pass can independently research, review,
|
||||
transform, or critique work. Custom subagents should receive their own `skills`
|
||||
field; do not assume they inherit every main-agent skill.
|
||||
|
||||
Use a deterministic tool only for exact work: JSON validation, schema checks,
|
||||
filesystem transforms, API calls, calculations, sandbox commands, conversion
|
||||
commands, or database queries.
|
||||
|
||||
Do not write tools whose body is prompt text, canned answers, or another hidden
|
||||
LLM call. That logic belongs in a DeepAgents skill or a subagent.
|
||||
|
||||
## Skill Files
|
||||
|
||||
Each skill directory must contain `SKILL.md` with frontmatter:
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: market-research
|
||||
description: Run market research with source review, competitor mapping, and cited synthesis. Use for market questions, competitive analysis, or customer research.
|
||||
---
|
||||
# Market Research
|
||||
|
||||
Follow this workflow...
|
||||
```
|
||||
|
||||
The `description` is the trigger text visible to the LLM before full skill
|
||||
loading. Keep it concrete. Put long examples or data dictionaries into
|
||||
`references/`, scripts into `scripts/`, and reusable assets into `assets/`.
|
||||
|
||||
## Backend Rules
|
||||
|
||||
DeepAgents' default state backend is not enough for hosted agents because file
|
||||
outputs can disappear into graph state. Always pass `backend=ctx.workspace_backend()`
|
||||
for generated agents that read or write files.
|
||||
|
||||
Use `skills=skill_sources or None`, not an empty list, so agents without a
|
||||
`skills/` directory still build cleanly.
|
||||
113
agent_builder/skills/workspace-artifact-safety/SKILL.md
Normal file
113
agent_builder/skills/workspace-artifact-safety/SKILL.md
Normal file
@@ -0,0 +1,113 @@
|
||||
---
|
||||
name: workspace-artifact-safety
|
||||
description: Write A2A agents that use ctx.workspace_backend, workspace grants, artifacts, sandbox commands, and file paths safely. Use for agents that read files, create outputs, run subprocesses, convert media, or return downloadable artifacts.
|
||||
---
|
||||
# Workspace And Artifact Safety
|
||||
|
||||
Generated agents run with explicit workspace grants. File work must fit those
|
||||
grants and produce artifacts callers can find.
|
||||
|
||||
## Workspace Access
|
||||
|
||||
If `agent.py` uses `ctx.workspace`, `ctx.workspace_backend()`, or a DeepAgents
|
||||
graph with file tools, declare workspace access on the class:
|
||||
|
||||
```python
|
||||
from a2a_pack import WorkspaceAccess, WorkspaceMode
|
||||
|
||||
workspace_access = WorkspaceAccess.dynamic(
|
||||
max_files=128,
|
||||
allowed_modes=(WorkspaceMode.READ_ONLY, WorkspaceMode.READ_WRITE_OVERLAY),
|
||||
require_reason=False,
|
||||
)
|
||||
```
|
||||
|
||||
Pass the backend into DeepAgents:
|
||||
|
||||
```python
|
||||
backend = ctx.workspace_backend()
|
||||
graph = create_deep_agent(model=model, backend=backend, skills=skill_sources or None)
|
||||
```
|
||||
|
||||
Write durable model-created files under `/workspace/outputs/...` in prompts.
|
||||
When writing through `ctx.workspace`, use workspace-relative paths such as
|
||||
`outputs/report.md`.
|
||||
|
||||
## Artifact Pattern
|
||||
|
||||
For user-visible outputs, return a small JSON object and emit an artifact:
|
||||
|
||||
```python
|
||||
async def save_report(ctx: RunContext[NoAuth], report: str) -> dict[str, str]:
|
||||
ref = await ctx.write_artifact(
|
||||
"report.md",
|
||||
report.encode("utf-8"),
|
||||
"text/markdown",
|
||||
)
|
||||
await ctx.emit_artifact(ref)
|
||||
return {"artifact_uri": ref.uri, "name": ref.name}
|
||||
```
|
||||
|
||||
For binary files, use the correct media type:
|
||||
|
||||
```python
|
||||
ref = await ctx.write_artifact("frames.zip", zip_bytes, "application/zip")
|
||||
await ctx.emit_artifact(ref)
|
||||
```
|
||||
|
||||
## Bounded Subprocesses
|
||||
|
||||
Media, rendering, archive, and data conversion agents must not run unbounded
|
||||
commands. Use explicit timeouts and return structured errors:
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
|
||||
|
||||
async def run_checked(cmd: list[str], timeout_s: int = 120) -> dict[str, str | int]:
|
||||
try:
|
||||
proc = await asyncio.create_subprocess_exec(
|
||||
*cmd,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
)
|
||||
stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=timeout_s)
|
||||
except asyncio.TimeoutError:
|
||||
return {"ok": 0, "error": "command timed out", "timeout_s": timeout_s}
|
||||
return {
|
||||
"ok": 1 if proc.returncode == 0 else 0,
|
||||
"returncode": proc.returncode,
|
||||
"stdout": stdout.decode("utf-8", errors="replace")[-4000:],
|
||||
"stderr": stderr.decode("utf-8", errors="replace")[-4000:],
|
||||
}
|
||||
```
|
||||
|
||||
If a nonzero command still produced a usable output, preserve the output and
|
||||
include the command failure as a warning instead of discarding the file.
|
||||
|
||||
## Scope Expansion
|
||||
|
||||
Only use `ctx.request_scope(...)` when the public `@skill` declares
|
||||
`allow_scope_expansion=True`. Explain the reason in plain language and keep
|
||||
the requested read/write patterns narrow.
|
||||
|
||||
```python
|
||||
@skill(
|
||||
description="Analyze a protected folder after the caller approves access",
|
||||
allow_scope_expansion=True,
|
||||
)
|
||||
async def analyze_folder(ctx: RunContext[NoAuth], folder: str) -> dict[str, str]:
|
||||
await ctx.request_scope(
|
||||
reason=f"Need to read {folder} to analyze the requested files",
|
||||
read=(f"{folder.rstrip('/')}/**",),
|
||||
mode="read_only",
|
||||
)
|
||||
return {"status": "approved"}
|
||||
```
|
||||
|
||||
## Safety Checklist
|
||||
|
||||
Do not hard-code absolute local paths. Do not expose arbitrary shell command
|
||||
strings as public inputs. Do not write secrets to artifacts. Do not use
|
||||
`wants_cp_jwt=True` for normal agents. Do not assume a file exists; check and
|
||||
return a structured error that the caller can act on.
|
||||
Reference in New Issue
Block a user