Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
97a7770
fix(store): the local object store writes atomically and keeps conten…
earakely-scale Sep 30, 2026
8ef1ddf
feat(store): the local object store issues HTTPS transfer grants
earakely-scale Sep 30, 2026
a9af97e
fix(store): keep the local CA's public trust files in their own reada…
earakely-scale Sep 30, 2026
f29d826
feat: agents on the local sandbox provider move objects through the l…
earakely-scale Sep 30, 2026
e91eea9
fix(store): a recorded content type names the file it describes, and …
earakely-scale Sep 30, 2026
0cbb1ac
Merge remote-tracking branch 'origin/edgararakelyan/local-store-atomi…
earakely-scale Sep 30, 2026
38846c9
fix(store): a process keeps one local CA, and grant uploads bound the…
earakely-scale Sep 30, 2026
fdac845
Merge remote-tracking branch 'origin/edgararakelyan/local-transfer-gr…
earakely-scale Sep 30, 2026
1271813
fix(store): a write-once put stays exclusive on a filesystem without …
earakely-scale Sep 30, 2026
dd586ab
Merge remote-tracking branch 'origin/edgararakelyan/local-store-atomi…
earakely-scale Sep 30, 2026
93a23ac
Merge remote-tracking branch 'origin/edgararakelyan/local-transfer-gr…
earakely-scale Sep 30, 2026
ba00e22
fix: local containers get the local CA only where local grants are ha…
earakely-scale Sep 30, 2026
5896423
fix(store): a live writer keeps its claim, and recorded types survive…
earakely-scale Sep 30, 2026
0214a47
Merge remote-tracking branch 'origin/edgararakelyan/local-store-atomi…
earakely-scale Sep 30, 2026
379d392
Merge remote-tracking branch 'origin/edgararakelyan/local-transfer-gr…
earakely-scale Sep 30, 2026
a2d2793
fix(store): claim a key without hard links with a lock that dies with…
earakely-scale Sep 30, 2026
14081e1
Merge remote-tracking branch 'origin/edgararakelyan/local-store-atomi…
earakely-scale Sep 30, 2026
f051005
Merge remote-tracking branch 'origin/edgararakelyan/local-transfer-gr…
earakely-scale Sep 30, 2026
340e1e6
fix: a trusted local container that fails to start is removed, and a …
earakely-scale Sep 30, 2026
d46296d
fix(store): a key's bytes and type are written and read under its loc…
earakely-scale Sep 30, 2026
67e8cb0
Merge the local store's key locks into the grant server, whose downlo…
earakely-scale Sep 30, 2026
45a48f4
Merge remote-tracking branch 'origin/edgararakelyan/local-transfer-gr…
earakely-scale Sep 30, 2026
7bf3aa2
fix(store): a grant download sends the bytes and type of one write
earakely-scale Sep 30, 2026
1b8b548
fix(store): a failed overwrite keeps the old type, and the sweep spar…
earakely-scale Sep 30, 2026
26ffcb7
Merge remote-tracking branch 'origin/edgararakelyan/local-store-atomi…
earakely-scale Sep 30, 2026
35ea9bc
fix(store): an overwrite that lands is never reported as failed, and …
earakely-scale Sep 30, 2026
cea9c1b
Merge remote-tracking branch 'origin/edgararakelyan/local-store-atomi…
earakely-scale Sep 30, 2026
0ee81fb
Merge remote-tracking branch 'origin/edgararakelyan/local-transfer-gr…
earakely-scale Sep 30, 2026
f48f5a8
Merge remote-tracking branch 'origin/main' into edgararakelyan/local-…
earakely-scale Sep 30, 2026
5c4c9b3
Merge remote-tracking branch 'origin/edgararakelyan/local-transfer-gr…
earakely-scale Sep 30, 2026
3ceb316
feat(store): each object store sets how long its transfer grants last…
earakely-scale Sep 30, 2026
9d8b992
fix(store): a local run leaves a store's own grant default alone, and…
earakely-scale Sep 30, 2026
3deb6cb
feat(protocol): SDK agents serve a staging extension agent-env can mo…
earakely-scale Oct 1, 2026
d31ce4a
Merge remote-tracking branch 'origin/main' into edgararakelyan/local-…
earakely-scale Oct 1, 2026
1e20c00
Merge remote-tracking branch 'origin/edgararakelyan/local-transfer-gr…
earakely-scale Oct 1, 2026
480b706
Merge remote-tracking branch 'origin/edgararakelyan/local-provider-tr…
earakely-scale Oct 1, 2026
4f34c93
Merge branch 'edgararakelyan/agent-staging-extension' into edgararake…
earakely-scale Oct 1, 2026
5d23eff
Merge branch 'edgararakelyan/agent-staging-extension' into edgararake…
earakely-scale Oct 1, 2026
cc3ef58
feat(protocol): make redacting_request_urls public, for callers that …
earakely-scale Oct 1, 2026
23b5b8d
feat(a2a): agents the object store's grants cannot reach get their ob…
earakely-scale Oct 1, 2026
ff1d3d4
fix(protocol): staging keeps its directories private, each server to …
earakely-scale Oct 1, 2026
f9f506a
Merge branch 'edgararakelyan/agent-staging-extension' into edgararake…
earakely-scale Oct 1, 2026
39cc287
fix(a2a): a staged pull stops at its grant's size, a rewritten increm…
earakely-scale Oct 1, 2026
e15fb36
fix(a2a): a changelog drain that keeps failing while a prompt runs is…
earakely-scale Oct 1, 2026
4caa425
fix(protocol): a server's own staging directory is removed when it exits
earakely-scale Oct 2, 2026
7adbb47
fix(a2a): a drain whose reason for failing changes says so, and rewri…
earakely-scale Oct 2, 2026
3d7d641
Merge branch 'edgararakelyan/agent-staging-extension' into edgararake…
earakely-scale Oct 2, 2026
a32835d
fix(a2a): objects that outgrow the agent's advertised staging limit a…
earakely-scale Oct 2, 2026
0df0dba
fix(task_step): a deploy_agent step that fails after its sandbox exis…
earakely-scale Oct 2, 2026
d2c1432
fix(protocol): staging tightens its own directories even when they we…
earakely-scale Oct 2, 2026
0f47fd8
Merge branch 'edgararakelyan/agent-staging-extension' into edgararake…
earakely-scale Oct 2, 2026
72e35d4
Merge remote-tracking branch 'origin/main' into edgararakelyan/local-…
earakely-scale Oct 5, 2026
98f20a3
refactor(store): the local object store and its grant server live tog…
earakely-scale Oct 5, 2026
dc4ed3b
Merge remote-tracking branch 'origin/edgararakelyan/local-transfer-gr…
earakely-scale Oct 5, 2026
e3e8f0c
Merge remote-tracking branch 'origin/edgararakelyan/local-provider-tr…
earakely-scale Oct 5, 2026
826fe0c
Merge remote-tracking branch 'origin/edgararakelyan/store-grant-lifet…
earakely-scale Oct 5, 2026
e653679
Merge main into local-transfer-grants
earakely-scale Oct 5, 2026
c7fd1a6
Merge local-transfer-grants (with main) into local-provider-transfer-…
earakely-scale Oct 5, 2026
f8e8f46
Merge local-provider-transfer-grants (with main) into store-grant-lif…
earakely-scale Oct 5, 2026
e0fd15b
Merge store-grant-lifetime (with main) into staged-transfers
earakely-scale Oct 5, 2026
9ba5dea
fix(store): a HEAD through a local read grant reports the size and ty…
earakely-scale Oct 5, 2026
af900c0
Merge remote-tracking branch 'origin/edgararakelyan/local-transfer-gr…
earakely-scale Oct 5, 2026
9b82a53
Merge remote-tracking branch 'origin/edgararakelyan/local-provider-tr…
earakely-scale Oct 5, 2026
29e7837
Merge remote-tracking branch 'origin/edgararakelyan/store-grant-lifet…
earakely-scale Oct 5, 2026
c18f840
Merge remote-tracking branch 'origin/main' into edgararakelyan/local-…
earakely-scale Oct 5, 2026
0b5313f
Merge remote-tracking branch 'origin/edgararakelyan/local-provider-tr…
earakely-scale Oct 5, 2026
9809287
Merge remote-tracking branch 'origin/edgararakelyan/store-grant-lifet…
earakely-scale Oct 5, 2026
7264979
Merge main into local-provider-transfer-grants
earakely-scale Oct 5, 2026
fa55fea
Merge commit '7264979' into edgararakelyan/store-grant-lifetime
earakely-scale Oct 5, 2026
f518cc3
Merge main into store-grant-lifetime
earakely-scale Oct 5, 2026
20a32b8
Merge remote-tracking branch 'origin/edgararakelyan/store-grant-lifet…
earakely-scale Oct 5, 2026
692118d
Merge main into staged-transfers
earakely-scale Oct 5, 2026
1f7e1f2
fix(a2a): a staged push sends each object at the size the call descri…
earakely-scale Oct 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/agent_env/a2a_agent/a2a_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
invoke_transfer,
skill_add_call,
)
from agent_env.a2a_agent.staging import transfer_store
from agent_env.providers.sandbox_providers.local_sandbox import (
LOCAL_TRUST_ENV,
LocalSandbox,
Expand Down Expand Up @@ -197,10 +198,13 @@ async def register_skill(
"""Send one skill, as SKILL.md text inline or the objects under ``object_url`` as a
bundle of read grants; returns the agent's answer."""
add_method, add_path = A2AAgent.operation(A2AAgent._skill_extension(deployed), "add")
store = transfer_store(
get_config().get_object_store(), deployed.a2a_url, deployed.agent_card, sandbox_type=deployed.sandbox_type
)
call = await asyncio.to_thread(
skill_add_call,
add_method,
get_config().get_object_store(),
store,
name=name,
description=description,
skill_md=skill_md,
Expand All @@ -213,6 +217,7 @@ async def register_skill(
verb="POST",
operation=f"skill add ({name})",
timeout=TRANSFER_TIMEOUT_SECONDS,
store=store,
)

@staticmethod
Expand Down
45 changes: 33 additions & 12 deletions src/agent_env/a2a_agent/object_transfer.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
from pydantic import BaseModel, ValidationError

from agent_env.a2a_agent.protocol import raise_for_extension_status
from agent_env.a2a_agent.staging import StagedObjectStore
from agent_env.store.base import GrantUnavailableError
from agent_env.store.object_store import DEFAULT_CONTENT_TYPE, ObjectStore
from agent_env.store.object_store.local.grant_server import unreachable_hint
Expand All @@ -49,6 +50,11 @@
_MEDIA_TYPE = re.compile(r"[!#$&^_.+\-|~0-9a-z]+/[!#$&^_.+\-|~0-9a-z]+")
_INCREMENT_NAME = re.compile(r"^(?P<sequence>[0-9]{6})(?:\.[A-Za-z0-9][A-Za-z0-9._-]*)?$")
_TRANSFER_UNAVAILABLE = "transfer_unavailable" # the SDK's code for a store it could not reach
_STAGING_UNREACHABLE = (
"The object store's grants do not reach this agent, so its objects were staged on the agent's own "
"server, and the agent could not reach them there through its own URL. Its sandbox provider may not let "
"a sandbox call its own public URL; an object store whose grants reach the agent avoids staging."
)

# The time budget of one transfer, outermost first: a grant (the issuing store's
# grant_lifetime_seconds, 12 hours unless configured) outlives agent-env's wait for the agent's
Expand Down Expand Up @@ -163,21 +169,34 @@ async def invoke_transfer(
operation: str,
timeout: float,
response_model: type[_Response] | None = None,
store: ObjectStore | None = None,
) -> Any:
"""Send ``call`` and return the agent's answer, validated against ``response_model`` when
the call carried grants. An error never quotes the answer to such a call, which may echo a
grant."""
async with httpx.AsyncClient() as client:
send = client.post if verb == "POST" else client.put
resp = await send(url, json=call.payload, timeout=timeout)
grant. ``store`` is the store the call's grants came from: when it stages them on the agent,
the objects the agent reads are pushed first and the ones it writes are in the store on return."""
staged = store if isinstance(store, StagedObjectStore) and call.mode == "objects" else None
try:
raise_for_extension_status(resp, operation=operation, include_body=call.mode == "legacy")
except httpx.HTTPStatusError as exc:
hint = unreachable_hint(call.payload) if call.mode == "objects" else None
if hint is None or not str(exc).endswith(f": {_TRANSFER_UNAVAILABLE}"):
raise
raise httpx.HTTPStatusError(f"{exc}. {hint}", request=exc.request, response=exc.response) from exc
body = resp.json()
if staged is not None:
await staged.push()
async with httpx.AsyncClient() as client:
send = client.post if verb == "POST" else client.put
resp = await send(url, json=call.payload, timeout=timeout)
try:
raise_for_extension_status(resp, operation=operation, include_body=call.mode == "legacy")
except httpx.HTTPStatusError as exc:
hint = _STAGING_UNREACHABLE if staged is not None else (
unreachable_hint(call.payload) if call.mode == "objects" else None
)
if hint is None or not str(exc).endswith(f": {_TRANSFER_UNAVAILABLE}"):
raise
raise httpx.HTTPStatusError(f"{exc}. {hint}", request=exc.request, response=exc.response) from exc
body = resp.json()
if staged is not None:
await staged.pull()
finally:
if staged is not None:
await staged.release()
Comment thread
greptile-apps[bot] marked this conversation as resolved.
if call.mode == "objects" and response_model is not None:
return parse_response(response_model, body, operation=operation)
return body
Expand Down Expand Up @@ -538,9 +557,10 @@ async def fetch_trajectory(
*,
upload: TrajectoryUpload | None = None,
timeout: float | None = None,
store: ObjectStore | None = None,
) -> FetchedTrajectory:
"""POST one trajectory ``get``. With ``upload`` the agent uploads the trajectory through its
grant and the answer is checked. The default wait covers that upload."""
grant, issued by ``store``, and the answer is checked. The default wait covers that upload."""
payload: dict[str, Any] = dict(selector)
if upload is not None:
objects = TrajectoryWriteObjects(trajectory=upload.write)
Expand All @@ -555,6 +575,7 @@ async def fetch_trajectory(
operation="trajectory get",
timeout=timeout,
response_model=TrajectoryObjectsResponse,
store=store,
)
if upload is not None:
return FetchedTrajectory(object_url=upload.object_url)
Expand Down
Loading
Loading