tools: expose a2a_pack source to the inner LLM loop
All checks were successful
build / build (push) Successful in 3s

list_a2a_pack(subdir) / read_a2a_pack(path) let the builder browse the
installed SDK at runtime — the same package the scaffolded agents
import. Path-traversal guarded; reads capped at 64 KiB.

Why: the system prompt only carries an abridged example. When the LLM
needed to recall what RunContext exposes (ctx.workspace API, ctx.ask
vs ctx.collect, runtime.apt_packages…) it would guess. Now it can
read the actual source.

Prompt updated to point at the new tools.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
robert
2026-05-15 22:08:54 -03:00
parent abc4b96455
commit 80a5d3bce4
2 changed files with 99 additions and 0 deletions

View File

@@ -94,6 +94,23 @@ Your tools:
- cp_deploy_tarball(name, version="0.1.0", public=True) - cp_deploy_tarball(name, version="0.1.0", public=True)
— ship it to the platform; returns the — ship it to the platform; returns the
public URL public URL
- cp_refresh_agent(name) — force the control plane to re-fetch
the agent's live card after an
out-of-band redeploy
- list_a2a_pack(subdir="") — browse the installed a2a_pack SDK
- read_a2a_pack(path) — read SDK source. Use these when
you're unsure what's exposed. The
code you scaffold runs on this
exact SDK, so reading it is the
authoritative reference — start
with ``read_a2a_pack("agent.py")``
for ``@skill`` semantics,
``context.py`` for ``RunContext``
(workspace, sandbox, emit_progress,
ask, collect, request_scope),
``runtime.py`` for ``AgentRuntime``
fields (apt_packages, pricing,
egress, etc.).
Discipline: Discipline:

View File

@@ -352,9 +352,91 @@ def build_tools(ctx: ToolContext) -> list[Any]:
"card": body.get("card") or {}, "card": body.get("card") or {},
}) })
@tool
def list_a2a_pack(subdir: str = "") -> str:
"""List ``.py`` files under the installed ``a2a_pack`` package.
Use this when you need to recall what the SDK actually exposes
— what decorators exist, what ``RunContext`` methods are
available, how ``WorkspaceClient`` is shaped, what the
``AgentRuntime`` fields are, etc. The agent code you scaffold
runs on this exact package, so reading it is the authoritative
reference (the system-prompt examples are intentionally
abridged).
``subdir`` is an optional path segment relative to the package
root (``""``, ``"serve"``, ``"mcp"`` …). Returns JSON:
``{root, files: [{path, size}]}``.
"""
try:
import a2a_pack
import os as _os
base = _os.path.dirname(_os.path.abspath(a2a_pack.__file__))
except Exception as exc: # noqa: BLE001
return json.dumps({"error": f"a2a_pack not importable: {exc}"})
if subdir.startswith("/") or ".." in subdir.split("/"):
return json.dumps({"error": "subdir must be relative; '..' disallowed"})
root = _os.path.join(base, subdir) if subdir else base
if not _os.path.commonpath([base, _os.path.abspath(root)]) == base:
return json.dumps({"error": "subdir escapes package root"})
if not _os.path.isdir(root):
return json.dumps({"error": f"not a directory: {subdir}"})
out: list[dict[str, Any]] = []
for dirpath, _, filenames in _os.walk(root):
for fname in filenames:
if not fname.endswith(".py"):
continue
full = _os.path.join(dirpath, fname)
rel = _os.path.relpath(full, base)
try:
size = _os.path.getsize(full)
except OSError:
continue
out.append({"path": rel, "size": size})
out.sort(key=lambda r: r["path"])
return json.dumps({"root": base, "files": out})
@tool
def read_a2a_pack(path: str) -> str:
"""Read a file from the installed ``a2a_pack`` package.
Path is relative to the package root, e.g. ``agent.py``,
``context.py``, ``runtime.py``, ``workspace.py``, ``card.py``,
``mcp/server.py``. Returns JSON: ``{path, content}``. Capped
at 64 KiB; reach for ``list_a2a_pack`` first if you need to
chase a deeper file.
"""
try:
import a2a_pack
import os as _os
base = _os.path.dirname(_os.path.abspath(a2a_pack.__file__))
except Exception as exc: # noqa: BLE001
return json.dumps({"error": f"a2a_pack not importable: {exc}"})
if path.startswith("/") or ".." in path.split("/"):
return json.dumps({"error": "path must be relative; '..' disallowed"})
full = _os.path.abspath(_os.path.join(base, path))
if _os.path.commonpath([base, full]) != base:
return json.dumps({"error": "path escapes package root"})
if not _os.path.isfile(full):
return json.dumps({"error": f"not a file: {path}"})
try:
with open(full, "r", encoding="utf-8") as fh:
content = fh.read(64 * 1024 + 1)
except OSError as exc:
return json.dumps({"error": str(exc)})
truncated = len(content) > 64 * 1024
if truncated:
content = content[: 64 * 1024]
return json.dumps({
"path": path,
"content": content,
"truncated": truncated,
})
return [ return [
list_agent_files, write_agent_file, read_agent_file, list_agent_files, write_agent_file, read_agent_file,
test_agent_in_sandbox, cp_deploy_tarball, cp_refresh_agent, test_agent_in_sandbox, cp_deploy_tarball, cp_refresh_agent,
list_a2a_pack, read_a2a_pack,
] ]