Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 4 additions & 5 deletions src/agent_env/artifact/artifacts/environment_universe.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,10 @@ def _serialize(self, handler: Any) -> dict[str, Any]:
data.pop("service_artifact_ids", None)
data.pop("metadata", None)
# Dual-write: the pinned refs get a twin, and only them.
# The scope line also asked for a `service_artifact_ids` twin on these
# modern docs — declined. That key means "unpinned, resolve at latest"
# to every reader, and v0.9.1 (pinned permanently by
# synthetic-artifacts-pipeline) acts on it, so writing it onto a doc
# that IS pinned tells that worker to ignore the pins. Nothing legacy
# No `service_artifact_ids` twin on these modern docs: that key means
# "unpinned, resolve at latest" to every reader, older releases
# included, so writing it onto a doc that IS pinned tells those
# readers to ignore the pins. Nothing legacy
# is dropped here: unpinned docs never reach this branch and still
# round-trip `service_artifact_ids` / `metadata` above, and
# get_environment_artifacts still falls back to them.
Expand Down
2 changes: 1 addition & 1 deletion src/agent_env/env/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ class DeployedEnv:
expires_at_utc: Optional[str] = None

def __post_init__(self) -> None:
# Only records from before #1137 (2026-09-24) lack their card: they keep the stored values.
# Only older records lack their card: they keep the stored values.
if self.environment_card and self.environment_card_url:
self.mcp_url = _mcp_url(self.environment_url, self.environment_card)
self.mcp_server_name = self.environment_card.get("name")
Expand Down
5 changes: 2 additions & 3 deletions src/agent_env/env/envs/service_db/Dockerfile.db-web
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
# Official pgweb image, pulled from its public GitHub Container Registry mirror.
# We avoid Docker Hub because anonymous pulls are rate-limited per IP (429s from
# CodeBuild's shared NAT). ghcr.io hosts the same image (identical digest) with
# no anonymous rate limit and no auth required, so no private ECR mirror is
# needed. The digest matches docker.io/sosedoff/pgweb:0.16.2. To bump: pick a
# CI runners behind a shared NAT). ghcr.io hosts the same image (identical
# digest) with no anonymous rate limit and no auth required. The digest matches docker.io/sosedoff/pgweb:0.16.2. To bump: pick a
# new tag from https://github.com/sosedoff/pgweb/pkgs/container/pgweb and update
# the tag + digest here.
FROM ghcr.io/sosedoff/pgweb:0.16.2@sha256:8f1ed22e10c9da0912169b98b62ddc54930dc39a5ae07b0f1354d2a93d44c6ed
9 changes: 4 additions & 5 deletions src/agent_env/env/gateway/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,8 @@

# Max end-to-end time for a single service's data load (seconds).
#
# The previous value of 600 was sized above a then-slowest prod load of ~430s.
# That is no longer the ceiling: a large universe's gmail component now takes 498s and slack
# 499s, leaving under 20% headroom on a number chosen to have plenty.
# A single component of a large universe can take close to 500s, so this leaves
# wide headroom.
#
# The margin matters more than the average because of how a load fails. A
# component that exceeds this does not retry — the environment comes up, reports
Expand All @@ -33,8 +32,8 @@
# Extra seconds granted per GB of payload, on top of the floor.
#
# Explicitly a heuristic, not a model. Measured load cost tracks RECORD COUNT, not bytes:
# github moved 3.5GB in 89s while gmail took 206s for 409MB and slack 207s for 86.6MB (~20x
# and ~94x worse per byte). So size is a weak proxy -- it over-grants to the big-blob
# a blob-heavy service can move GBs in about a minute while a record-heavy one takes minutes
# for well under 1GB. So size is a weak proxy -- it over-grants to the big-blob
# services that never needed it and under-grants to the small record-heavy ones that do.
# It is still strictly better than a flat cap, because the flat cap's failure mode was
# killing healthy work. A real fix is a stall timeout (fail on no progress rather than on
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export interface VerificationResult {
}

export interface VerifierOutput {
/** Stamped by agent-env rubrics_verifier (PR1 judge output format registry). */
/** Stamped by agent-env rubrics_verifier (judge output format registry). */
format?: JudgeOutputFormat;
results: VerificationResult[];
score: number;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ function rubricLabel(rubric: RubricCriterion): string {
* Pick the verifier output that actually grades the configured rubrics,
* not just whichever happens to come first in dict iteration order.
*
* Tasks like `mm-avatar-full-urls` write multiple entries to
* A task can write multiple entries to
* `metadata.verifications` — `verify-fs` (sandbox), `verify-response`
* (rubric), `aggregate`. Prefer entries stamped with
* `format: "rubric_binary"`; fall back to max rubric-id overlap
Expand Down
8 changes: 4 additions & 4 deletions src/agent_env/explorer/ui/src/lib/bar-downloads.smoke.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,17 +164,17 @@ function main(): void {
);
}

// The reported case: demo-bench-proof-c2, many steps + results.
// A task with many steps + results.
{
const many = Array.from({ length: 12 }, (_, i) => ({
agent_trajectory_s3_uri: `obj://halt/${i}`,
agent_trajectory_s3_uri: `obj://bucket/${i}`,
step_id: `step-${i}`,
}));
const downloads = buildBarDownloads(many, 'obj://halt/results.json');
const downloads = buildBarDownloads(many, 'obj://bucket/results.json');
assert(downloads.length === 13, 'all 12 trajectories plus results kept');
assert(
shouldCollapseBarDownloads(downloads.length),
'the reported task collapses into a dropdown',
'a many-step task collapses into a dropdown',
);
assert(
new Set(downloads.map(d => d.key)).size === downloads.length,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@
* and assert that classification + event extraction + sub-agent folding
* produce sensible output.
*
* Fixture default: `/tmp/traj.json` — pulled from the real run
* a captured agent run while diagnosing the
* "Cannot read properties of undefined (reading 'openinference.span.kind')"
* crash. Override with `CLAUDE_CLI_FIXTURE=<path>`.
* Fixture default: `/tmp/traj.json`, a captured agent run. Override with
* `CLAUDE_CLI_FIXTURE=<path>`.
*
* Runner: plain TS, throws on assertion failure. Run with any TS executor:
* npx tsx src/lib/parse-trajectory-claude-cli.smoke.ts
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@
* `looksLikeOpenCodeStreamJson`) and assert the event extraction contract.
*
* Default fixture is inline + synthetic but faithful to the real record shape
* (verified against the live run `task-v2env-186-778c8c2c-opencode-y5hm9olj`:
* records are `{type, timestamp, sessionID, part}`; tool calls are
* (records are `{type, timestamp, sessionID, part}`; tool calls are
* self-contained with `part.state={status,input,output,time}`; a turn ends on a
* `step_finish` whose `part.reason === 'stop'`). Point at a real pulled
* trajectory with `OPENCODE_FIXTURE=<path>`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* output.
*
* The fixture is a 22-span trajectory captured from a 3-sub-agent kimi-k2p5
* run via `/tmp/smoke_otel_bridge.py`. It exercises the OpenInference
* run. It exercises the OpenInference
* adapter on every span kind (AGENT/CHAIN/TOOL/LLM), nested sub-agent
* runs, and tool calls.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ function assert(cond: unknown, msg: string): void {
}

function main(): void {
// The real prod entry from aeh-0000000000000-000000000000000a (completed run, one retry).
// A completed run's entry, one retry.
const real = {
attempt: 1,
step_id: 'verify-response',
Expand Down Expand Up @@ -80,9 +80,8 @@ function main(): void {
}

{
// Timestamps are minute-precision, so a step failing in seconds produces ties.
// Shape from aeh-0000000000000-000000000000000b, whose verify-response attempts took
// 83s / 11s / 22s.
// Timestamps are minute-precision, so a step failing in seconds produces ties:
// here three verify-response attempts that took 83s / 11s / 22s.
const parsed = parseStepAttemptFailures([
{ ...real, attempt: 3, occurred_at_utc: '2026-07-29 00:49 UTC' },
{ ...real, attempt: 2, occurred_at_utc: '2026-07-29 00:49 UTC' },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,7 @@ async def _log_sandbox_started(
) -> None:
"""Log one structured line mapping Modal's container id to the sandbox and its tags.

Modal's Datadog metrics carry only ``container_id`` (the ``ta-`` task id), not sandbox tags, so
Modal's exported metrics carry only ``container_id`` (the ``ta-`` task id), not sandbox tags, so
this line is the join key from a container's usage to its run and pipeline step. Never raises:
the task id comes from a private Modal method.
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,8 +220,8 @@ def upload_trajectory(
Always uploaded from the worker side, never by the agent: sandbox VMs hold
static STS env vars with a fixed expiry, and long runs exceed it.

``name`` makes the object addressable by an id the caller already records
(#731); omit it to get a random one, which is what writing repeatedly under a
``name`` makes the object addressable by an id the caller already records;
omit it to get a random one, which is what writing repeatedly under a
single prefix needs, since a fixed name would overwrite the last upload.
"""

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,10 @@
- `probe_step_did_not_run`: no matching response — the upstream
`PromptAgentTaskStep` crashed before `context.prompt_responses.append(...)`.

The gemini wrapper currently passes all 12 modality probes (text, image
{png,jpeg,gif}, audio {wav,mpeg,mp4,ogg}, PDF, video/mp4, plus image/png
delivered via `FileWithUri` over `s3://` and presigned `https://`). New
A2A wrappers (claude-code, codex, grok) inherit this same probe set and
will report which subset they actually ingest.
There are 12 modality probes (text, image {png,jpeg,gif}, audio
{wav,mpeg,mp4,ogg}, PDF, video/mp4, plus image/png delivered via
`FileWithUri` over `s3://` and presigned `https://`). Every A2A agent gets
the same probe set, and the results report which subset it ingests.
"""

from __future__ import annotations
Expand Down
5 changes: 2 additions & 3 deletions src/agent_env/task_step/task_steps/apply_server_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,8 @@ def __init__(self, service: str, uri: str, args: dict[str, Any]):

def to_dict(self) -> dict:
# Dual-write. This feeds the `tasks` and `task_steps`
# documents — NOT the delivered bundle. The bundle mints its own dict
# from these attributes in export/exporter.py, so the permanently frozen
# `{'service': ...}` Harbor applier input is unaffected by the twin here.
# documents only. An exporter builds its own dict from these attributes,
# so a frozen `{'service': ...}` export format is unaffected by the twin here.
return {"service": self.service, "environment": self.service, "uri": self.uri, "args": self.args}

@classmethod
Expand Down
6 changes: 3 additions & 3 deletions src/agent_env/task_step/task_steps/collect_artifacts.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@
}

With seed CSV:
name,platform,expected_artifacts
VPC Endpoints,Terraform,"Dockerfile,run.sh,codebase.patch,test.patch,gold.patch,prompt.md,before.json,after.json,p2p_tests.json,f2p_tests.json,validation.json,progress.md"
name,expected_artifacts
example-task,"Dockerfile,run.sh,solution.patch,report.md"

Or with static fallback (no seed data needed):
{
Expand Down Expand Up @@ -714,7 +714,7 @@ async def _collect_via_controller(self, context, store, artifact_id, version):
# Read files via the CUA MCP server (cua_get_file / cua_bash) over the
# gateway's step/v1. These are MCP tools registered on the gateway on both
# Ubuntu and macOS — NOT endpoints on the macOS controller sidecar, whose
# /step only accepts a ScaleCuaAction and 500s on a call_tool payload.
# /step only accepts a computer-use action and 500s on a call_tool payload.
logger.info(f"Collecting artifacts via CUA MCP server (env_id={self.env_id}, gateway={deployed_env.environment_url})")

items = self._resolve_items(context)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,6 @@
enforcement by passing ``spec_conformance`` in ``ValidationGateAggregatorStep``'s
``required_gates`` (a step arg, not env metadata). No baked spec = ``skipped``. Execution
errors (unreachable MCP) still raise.

Keep the diff logic in sync with the universe-generation pipeline's
``synthetic_mcp_server_generation/validation/spec_conformance.py`` (same diff at generation
time); tool shape differs — MCP objects here, dicts there.
"""
Comment thread
polakamtejas marked this conversation as resolved.

from __future__ import annotations
Expand Down
2 changes: 1 addition & 1 deletion src/agent_env/task_step/task_steps/prompt_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -711,7 +711,7 @@ async def _fetch_trajectory(self, a2a_url: str, traj_ext: dict, a2a_server_task_
)
# Fetched by a2a_server_task_id, the id the A2A server produced; stored under
# target_a2a_task_id, the client message id persisted on the conversation as
# a2a_task_id, so the FE can resolve it (#731).
# a2a_task_id, so the FE can resolve it.
upload = None
if mode == "objects":
upload = await asyncio.to_thread(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,9 +194,9 @@ async def test_a_failed_steps_partial_writes_reach_the_doc_but_not_the_journal()

A step that writes and then fails gets its writes onto the instance through
``record_task_failure``'s wholesale ``context`` write, which is not journaled. Replay
therefore omits them and the journal is *not* a complete record of a failed run. The
stacked retry PR is expected to journal the failing step with ``status=failure`` first;
when it does, this test should start failing and be tightened to assert equality.
therefore omits them and the journal is *not* a complete record of a failed run. Once
the failing step is journaled with ``status=failure`` first, this test should start
failing and be tightened to assert equality.
"""
task_id = f"retry-resume-partial-test-{uuid.uuid4().hex[:8]}"
instance_id = f"{task_id}-deterministic"
Expand Down
4 changes: 2 additions & 2 deletions tst/unit/env/envs/test_multi_env_vm_sizing.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
"""A MultiEnv does not size its own box; cpu and memory both defer to the provider.

The providers already floor both per backend (#1045: 0.125 vCPU on Modal, where cpu is a
The providers already floor both per backend (0.125 vCPU on Modal, where cpu is a
burstable reservation, 0.5 on the VM backends; 8GB memory on either), and a number invented
here could only override a better-informed default. In container mode it was actively
wrong: the derived size landed on the gateway, a router, as if it hosted every service.

7 days of production logs showed nothing depending on the derived cpu except that gateway,
Nothing depended on the derived cpu except that gateway,
and the two callers that leaned on the derived memory were getting it from `cpu x 4096` --
an 11-service env on 2GB. An explicit value from the caller still wins on both axes.
"""
Expand Down
4 changes: 2 additions & 2 deletions tst/unit/env/gateway/triggers_time_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ async def test_event_anchored_never_fires_without_anchor():
], ids=["one-shot", "bounded-recurrence"])
@pytest.mark.asyncio
async def test_terminating_time_anchor_does_not_strand_dependent(anchor_when):
"""Greptile #765: a TIME anchor that retires must still resolve its dependents, even though
"""A TIME anchor that retires must still resolve its dependents, even though
`_fire_time` recomputes `_has_time_triggers` (in `finally`) *before* calling `_resolve_dependents`,
which early-returns on a cleared flag.

Expand Down Expand Up @@ -534,7 +534,7 @@ async def test_rearm_reanchors_trigger_that_was_firing():

@pytest.mark.asyncio
async def test_rearm_drops_capped_backlog_and_says_so(monkeypatch):
"""Greptile P1c: a re-arm that lands while a capped catch-up batch is firing drops the leftover
"""A re-arm that lands while a capped catch-up batch is firing drops the leftover
backlog — by design (those marks are on a timeline the new clock never reached) — but the drop must
NOT be silent: it emits a `reanchored` event naming the dropped mark, and the trigger re-anchors to
the new t0 rather than replaying the old axis."""
Expand Down
2 changes: 1 addition & 1 deletion tst/unit/env/test_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ def test_a_record_takes_its_mcp_url_and_server_name_from_its_card():

@pytest.mark.parametrize("card, card_url", [(None, _CARD_URL), (_CARD, None)], ids=["no-card", "no-card-url"])
def test_a_record_without_its_card_keeps_its_stored_mcp_url_and_name(card, card_url):
"""Records written before #1137 carry no card, so their stored values stand."""
"""Older records carry no card, so their stored values stand."""
record = DeployedEnv.from_dict({"env_id": "e", "env_version": 1, "gateway_url": "https://old", "sandbox_id": "sb",
"mcp_url": "https://old/mcp", "mcp_server_name": "old", "environment_card": card, "environment_card_url": card_url})
assert (record.mcp_url, record.mcp_server_name) == ("https://old/mcp", "old")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ async def _spy_acquire(self, ctx):

assert len(gp._container_sandboxes) == 4

# PR 2: the deploy routes DB state through the provider.
# The deploy routes DB state through the provider.
# servicedb container env comes from the provider's store-spec
db_call = next(c for c in create_calls if c["port"] == SERVICE_DB_PORT)
assert db_call["env"] == LocalPostgresStateProvider().store_spec(["slack", "email"]).env
Expand Down
10 changes: 5 additions & 5 deletions tst/unit/providers/sandbox_providers/modal_sandbox_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ async def test_create_container_wraps_modal_create_failure_with_context(_patched

with pytest.raises(RuntimeError) as ei:
await _patched_provider.create_container(
image_name="123456789012.dkr.ecr.us-west-2.amazonaws.com/agent-env/agent-images/mcp-server-airtable:v4",
image_name="123456789012.dkr.ecr.us-west-2.amazonaws.com/example/mcp-server:v1",
port=18765,
env={},
cpu=2.0,
Expand All @@ -54,7 +54,7 @@ async def test_create_container_wraps_modal_create_failure_with_context(_patched
msg = str(ei.value)
assert "Modal sandbox create failed" in msg
assert "_BareSandboxTimeoutError" in msg
assert "mcp-server-airtable:v4" in msg
assert "example/mcp-server:v1" in msg
assert "port=18765" in msg
assert "region=us-east-1" in msg
assert "i6pn=True" in msg
Expand All @@ -75,15 +75,15 @@ async def test_create_container_wraps_post_create_failure_and_terminates(_patche

with pytest.raises(RuntimeError) as ei:
await _patched_provider.create_container(
image_name="registry.example/mcp-server-openclaw-strava:v6",
image_name="registry.example/mcp-server-example:v1",
port=18765,
env={},
)

msg = str(ei.value)
assert "Modal sandbox post-create failed" in msg
assert "sb_id=sb-FAKEFAKEFAKEFAKEFAKE" in msg
assert "mcp-server-openclaw-strava:v6" in msg
assert "mcp-server-example:v1" in msg
fake_sb.terminate.aio.assert_awaited_once()


Expand Down Expand Up @@ -141,7 +141,7 @@ def mint(self, host):
return RegistryAuth(host, "u", "p")

ecr_host = "123456789012.dkr.ecr.us-west-2.amazonaws.com"
ecr = "123456789012.dkr.ecr.us-west-2.amazonaws.com/agent-env/agent-images/x:v1"
ecr = "123456789012.dkr.ecr.us-west-2.amazonaws.com/example/x:v1"
_patched_provider._ecr_pull_secret_name = "agent-env-ecr-reader"
p = lambda n: patch(f"agent_env.providers.sandbox_providers.modal_sandbox.modal.{n}") # noqa: E731

Expand Down
2 changes: 1 addition & 1 deletion tst/unit/providers/sandbox_providers/vm_sandbox_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ async def test_load_docker_images_downloads_to_file_before_load(signing_store):

Retrying `curl ... | gunzip | docker load` directly corrupts the stream
because curl can't rewind bytes already piped to stdout, so the retry flags
must only apply to a `-o file` download (Greptile P1 on PR #430).
must only apply to a `-o file` download.
"""
sandbox = _RecordingVmSandbox(images_stdout="myimage\n")
artifact = SimpleNamespace(tar_gz_object_url="s3://bucket/img.tar.gz", image_name="myimage:latest")
Expand Down
2 changes: 1 addition & 1 deletion tst/unit/serialization/golden/env/gateway_server.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
},
"id": "gateway-env",
"metadata": {
"owner": "env-pod"
"owner": "example-team"
},
"type": "gateway_server",
"version": 11
Expand Down
2 changes: 1 addition & 1 deletion tst/unit/serialization/golden/env/mcp_server.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"environment_name": "slack",
"id": "slack-env",
"metadata": {
"owner": "env-pod"
"owner": "example-team"
},
"service_name": "slack",
"type": "mcp_server",
Expand Down
2 changes: 1 addition & 1 deletion tst/unit/serialization/golden/env/multi.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
}
],
"metadata": {
"owner": "env-pod"
"owner": "example-team"
},
"type": "multi",
"version": 6,
Expand Down
2 changes: 1 addition & 1 deletion tst/unit/serialization/golden/env/service_db.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
},
"id": "service-db-env",
"metadata": {
"owner": "env-pod"
"owner": "example-team"
},
"type": "service_db",
"version": 3
Expand Down
Loading
Loading