agentenv-framework 0.9.1254__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agent_env/__init__.py +0 -0
- agent_env/a2a_agent/__init__.py +23 -0
- agent_env/a2a_agent/a2a_agent.py +600 -0
- agent_env/a2a_agent/conversation_store.py +356 -0
- agent_env/a2a_agent/object_transfer.py +609 -0
- agent_env/a2a_agent/protocol.py +238 -0
- agent_env/a2a_agent/store.py +201 -0
- agent_env/a2a_agent/validator.py +901 -0
- agent_env/artifact/__init__.py +48 -0
- agent_env/artifact/artifact.py +74 -0
- agent_env/artifact/artifacts/__init__.py +0 -0
- agent_env/artifact/artifacts/cli.py +83 -0
- agent_env/artifact/artifacts/docker_image.py +457 -0
- agent_env/artifact/artifacts/environment.py +89 -0
- agent_env/artifact/artifacts/environment_universe.py +156 -0
- agent_env/artifact/artifacts/file.py +214 -0
- agent_env/artifact/artifacts/file_artifact_universe.py +206 -0
- agent_env/artifact/artifacts/skill.py +206 -0
- agent_env/artifact/artifacts/vm_image.py +95 -0
- agent_env/artifact/ref.py +15 -0
- agent_env/artifact/registry.py +251 -0
- agent_env/artifact/store.py +295 -0
- agent_env/artifact/universe.py +38 -0
- agent_env/attribution.py +41 -0
- agent_env/bundle/__init__.py +4 -0
- agent_env/bundle/_fs.py +128 -0
- agent_env/bundle/authoring.py +239 -0
- agent_env/bundle/installed.py +175 -0
- agent_env/bundle/ledger.py +269 -0
- agent_env/bundle/materialize.py +204 -0
- agent_env/bundle/parse.py +594 -0
- agent_env/bundle/plan.py +414 -0
- agent_env/bundle/resolve.py +404 -0
- agent_env/bundle/run.py +361 -0
- agent_env/cli/__init__.py +69 -0
- agent_env/cli/__main__.py +10 -0
- agent_env/cli/_installers.py +576 -0
- agent_env/cli/_plugin_changes.py +947 -0
- agent_env/cli/a2a_agent/__init__.py +22 -0
- agent_env/cli/a2a_agent/add_skill.py +97 -0
- agent_env/cli/a2a_agent/deploy.py +60 -0
- agent_env/cli/a2a_agent/get.py +15 -0
- agent_env/cli/a2a_agent/get_instance.py +26 -0
- agent_env/cli/a2a_agent/put.py +78 -0
- agent_env/cli/a2a_agent/validate.py +56 -0
- agent_env/cli/artifact/__init__.py +20 -0
- agent_env/cli/artifact/cli.py +83 -0
- agent_env/cli/artifact/environment.py +44 -0
- agent_env/cli/artifact/environment_universe.py +163 -0
- agent_env/cli/artifact/file_artifact_universe.py +281 -0
- agent_env/cli/artifact/skill.py +128 -0
- agent_env/cli/banner.py +25 -0
- agent_env/cli/config.py +263 -0
- agent_env/cli/env/__init__.py +31 -0
- agent_env/cli/env/deploy.py +112 -0
- agent_env/cli/env/gateway.py +57 -0
- agent_env/cli/env/get_instance.py +40 -0
- agent_env/cli/env/mcp_server.py +410 -0
- agent_env/cli/env/multi.py +282 -0
- agent_env/cli/env/service_db.py +99 -0
- agent_env/cli/env/snapshot.py +35 -0
- agent_env/cli/env/state/__init__.py +6 -0
- agent_env/cli/env/state/init.py +108 -0
- agent_env/cli/env/state/teardown.py +44 -0
- agent_env/cli/env/website.py +216 -0
- agent_env/cli/env/website_browser.py +67 -0
- agent_env/cli/eval/__init__.py +16 -0
- agent_env/cli/eval/add_tasks.py +63 -0
- agent_env/cli/eval/create.py +49 -0
- agent_env/cli/eval/run.py +194 -0
- agent_env/cli/identity/__init__.py +5 -0
- agent_env/cli/identity/client_identity.py +37 -0
- agent_env/cli/plugin.py +438 -0
- agent_env/cli/run.py +285 -0
- agent_env/cli/task/__init__.py +21 -0
- agent_env/cli/task/create.py +109 -0
- agent_env/cli/task/get.py +17 -0
- agent_env/cli/task/get_instance.py +34 -0
- agent_env/cli/task/run.py +615 -0
- agent_env/cli/task/validate.py +31 -0
- agent_env/cli/up.py +123 -0
- agent_env/cli/utils.py +183 -0
- agent_env/config/__init__.py +50 -0
- agent_env/config/describe.py +1067 -0
- agent_env/config/errors.py +13 -0
- agent_env/config/loader.py +245 -0
- agent_env/config/model.py +154 -0
- agent_env/config/paths.py +25 -0
- agent_env/config/plugin_tables.py +101 -0
- agent_env/config/provenance.py +134 -0
- agent_env/config/runtime.py +1064 -0
- agent_env/config/snapshot.py +81 -0
- agent_env/entity_refs.py +186 -0
- agent_env/env/__init__.py +49 -0
- agent_env/env/env.py +433 -0
- agent_env/env/env_artifact_store.py +87 -0
- agent_env/env/envs/__init__.py +7 -0
- agent_env/env/envs/_deployment.py +281 -0
- agent_env/env/envs/gateway_server.py +32 -0
- agent_env/env/envs/mcp_server.py +387 -0
- agent_env/env/envs/multi_env.py +780 -0
- agent_env/env/envs/service_db/Dockerfile +20 -0
- agent_env/env/envs/service_db/Dockerfile.db-mcp +42 -0
- agent_env/env/envs/service_db/Dockerfile.db-web +8 -0
- agent_env/env/envs/service_db.py +92 -0
- agent_env/env/envs/website.py +294 -0
- agent_env/env/envs/website_browser/Dockerfile +40 -0
- agent_env/env/envs/website_browser/__init__.py +17 -0
- agent_env/env/envs/website_browser/entrypoint.sh +13 -0
- agent_env/env/gateway/Dockerfile +14 -0
- agent_env/env/gateway/__init__.py +37 -0
- agent_env/env/gateway/clock.py +184 -0
- agent_env/env/gateway/constants.py +190 -0
- agent_env/env/gateway/entrypoint.py +156 -0
- agent_env/env/gateway/gateway.py +1095 -0
- agent_env/env/gateway/get_time.py +60 -0
- agent_env/env/gateway/requirements.txt +4 -0
- agent_env/env/gateway/triggers.py +1193 -0
- agent_env/env/legacy_protocol.py +146 -0
- agent_env/env/registry.py +93 -0
- agent_env/env/snapshot_store.py +372 -0
- agent_env/env/store.py +411 -0
- agent_env/eval/__init__.py +14 -0
- agent_env/eval/eval.py +68 -0
- agent_env/eval/store.py +116 -0
- agent_env/examples/hello/README.md +9 -0
- agent_env/examples/hello/artifacts/greeting/check.sh +1 -0
- agent_env/examples/hello/artifacts/greeting/hello.txt +1 -0
- agent_env/examples/hello/tasks/hello.json +10 -0
- agent_env/explorer/__init__.py +12 -0
- agent_env/explorer/app.py +277 -0
- agent_env/explorer/openapi_docs.py +242 -0
- agent_env/explorer/plugin.py +95 -0
- agent_env/explorer/routers/__init__.py +1 -0
- agent_env/explorer/routers/common.py +174 -0
- agent_env/explorer/routers/conversations.py +25 -0
- agent_env/explorer/routers/objects.py +112 -0
- agent_env/explorer/routers/runs.py +475 -0
- agent_env/plugins/__init__.py +59 -0
- agent_env/plugins/_cli.py +244 -0
- agent_env/plugins/_discovery.py +97 -0
- agent_env/plugins/_inventory.py +262 -0
- agent_env/plugins/_registration.py +268 -0
- agent_env/plugins/_report.py +47 -0
- agent_env/plugins/_requirements.py +55 -0
- agent_env/providers/__init__.py +37 -0
- agent_env/providers/env_providers/__init__.py +12 -0
- agent_env/providers/env_providers/constants.py +13 -0
- agent_env/providers/env_providers/env_gateway_provider.py +1354 -0
- agent_env/providers/env_providers/env_provider.py +237 -0
- agent_env/providers/env_providers/env_server_provider.py +133 -0
- agent_env/providers/env_state/__init__.py +49 -0
- agent_env/providers/env_state/env_state_provider.py +554 -0
- agent_env/providers/env_state/local_postgres.py +390 -0
- agent_env/providers/env_state/store.py +112 -0
- agent_env/providers/sandbox_providers/__init__.py +29 -0
- agent_env/providers/sandbox_providers/chained_sandbox_provider.py +93 -0
- agent_env/providers/sandbox_providers/e2b/__init__.py +6 -0
- agent_env/providers/sandbox_providers/e2b/provider.py +376 -0
- agent_env/providers/sandbox_providers/e2b/sandbox.py +342 -0
- agent_env/providers/sandbox_providers/e2b/template.py +158 -0
- agent_env/providers/sandbox_providers/local_sandbox.py +408 -0
- agent_env/providers/sandbox_providers/modal_sandbox.py +500 -0
- agent_env/providers/sandbox_providers/modal_vm_sandbox.py +466 -0
- agent_env/providers/sandbox_providers/sandbox.py +387 -0
- agent_env/providers/sandbox_providers/sandbox_provider.py +614 -0
- agent_env/py.typed +0 -0
- agent_env/runner/__init__.py +16 -0
- agent_env/runner/local_runner.py +190 -0
- agent_env/runner/runner.py +127 -0
- agent_env/runner/store.py +94 -0
- agent_env/store/__init__.py +126 -0
- agent_env/store/_google.py +68 -0
- agent_env/store/base.py +33 -0
- agent_env/store/document_store/__init__.py +60 -0
- agent_env/store/document_store/document_store.py +519 -0
- agent_env/store/document_store/dynamodb_document_store.py +306 -0
- agent_env/store/document_store/evaluation.py +199 -0
- agent_env/store/document_store/firestore_mongo_document_store.py +242 -0
- agent_env/store/document_store/mongo_document_store.py +259 -0
- agent_env/store/document_store/sqlite_document_store.py +314 -0
- agent_env/store/ids.py +123 -0
- agent_env/store/image_store/__init__.py +34 -0
- agent_env/store/image_store/ecr_image_store.py +131 -0
- agent_env/store/image_store/google_credentials.py +113 -0
- agent_env/store/image_store/image_store.py +113 -0
- agent_env/store/image_store/local_registry_image_store.py +106 -0
- agent_env/store/image_store/oci_registry_credentials.py +196 -0
- agent_env/store/local_state.py +27 -0
- agent_env/store/object_store/__init__.py +24 -0
- agent_env/store/object_store/gcs_object_store.py +415 -0
- agent_env/store/object_store/local_object_store.py +143 -0
- agent_env/store/object_store/object_store.py +198 -0
- agent_env/store/object_store/s3_object_store.py +354 -0
- agent_env/store/query.py +112 -0
- agent_env/store/routing.py +609 -0
- agent_env/store/secret_store/__init__.py +19 -0
- agent_env/store/secret_store/aws_secrets_manager_secret_store.py +293 -0
- agent_env/store/secret_store/gcp_secret_manager_secret_store.py +319 -0
- agent_env/store/secret_store/local_secret_store.py +47 -0
- agent_env/store/secret_store/secret_store.py +19 -0
- agent_env/task/__init__.py +27 -0
- agent_env/task/interrupts.py +109 -0
- agent_env/task/registry.py +27 -0
- agent_env/task/step_journal.py +99 -0
- agent_env/task/store.py +940 -0
- agent_env/task/task.py +815 -0
- agent_env/task/teardown.py +142 -0
- agent_env/task_step/__init__.py +47 -0
- agent_env/task_step/context.py +195 -0
- agent_env/task_step/context_ops.py +224 -0
- agent_env/task_step/registry.py +182 -0
- agent_env/task_step/review_store.py +118 -0
- agent_env/task_step/snapshot_utils/__init__.py +2 -0
- agent_env/task_step/snapshot_utils/agent_state_capture.py +261 -0
- agent_env/task_step/snapshot_utils/snapshot_series.py +631 -0
- agent_env/task_step/store.py +129 -0
- agent_env/task_step/task_step.py +173 -0
- agent_env/task_step/task_steps/__init__.py +27 -0
- agent_env/task_step/task_steps/a2a_agent_validator/__init__.py +31 -0
- agent_env/task_step/task_steps/a2a_agent_validator/fixtures/README.md +158 -0
- agent_env/task_step/task_steps/a2a_agent_validator/fixtures/clip.m4a +0 -0
- agent_env/task_step/task_steps/a2a_agent_validator/fixtures/clip.mp3 +0 -0
- agent_env/task_step/task_steps/a2a_agent_validator/fixtures/clip.mp4 +0 -0
- agent_env/task_step/task_steps/a2a_agent_validator/fixtures/clip.ogg +0 -0
- agent_env/task_step/task_steps/a2a_agent_validator/fixtures/clip.wav +0 -0
- agent_env/task_step/task_steps/a2a_agent_validator/fixtures/document.pdf +0 -0
- agent_env/task_step/task_steps/a2a_agent_validator/fixtures/red.gif +0 -0
- agent_env/task_step/task_steps/a2a_agent_validator/fixtures/red.jpg +0 -0
- agent_env/task_step/task_steps/a2a_agent_validator/fixtures/red.png +0 -0
- agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_agent_card.py +103 -0
- agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_agent_config_identity.py +69 -0
- agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_agent_mcp.py +178 -0
- agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_core_protocol.py +67 -0
- agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_install.py +115 -0
- agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_litellm_attribution.py +124 -0
- agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_litellm_attribution_runtime.py +173 -0
- agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_modalities.py +270 -0
- agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_peer_agents.py +102 -0
- agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_role.py +151 -0
- agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_skill_config.py +194 -0
- agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_snapshot.py +111 -0
- agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_system_prompt.py +215 -0
- agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_trajectory.py +164 -0
- agent_env/task_step/task_steps/add_skills.py +240 -0
- agent_env/task_step/task_steps/apply_server_config.py +288 -0
- agent_env/task_step/task_steps/collect_artifacts.py +940 -0
- agent_env/task_step/task_steps/deploy_agent.py +626 -0
- agent_env/task_step/task_steps/deploy_env.py +197 -0
- agent_env/task_step/task_steps/deploy_human_agent.py +100 -0
- agent_env/task_step/task_steps/deploy_sandbox.py +170 -0
- agent_env/task_step/task_steps/env_card_validator/__init__.py +4 -0
- agent_env/task_step/task_steps/env_card_validator/verify_env_card.py +109 -0
- agent_env/task_step/task_steps/env_card_validator/verify_env_core_protocol.py +108 -0
- agent_env/task_step/task_steps/install_agent.py +376 -0
- agent_env/task_step/task_steps/load_artifact.py +775 -0
- agent_env/task_step/task_steps/mcp_cli_builder/__init__.py +4 -0
- agent_env/task_step/task_steps/mcp_cli_builder/build_mcp_cli.py +201 -0
- agent_env/task_step/task_steps/mcp_cli_builder/codegen.py +627 -0
- agent_env/task_step/task_steps/mcp_env_validator/__init__.py +44 -0
- agent_env/task_step/task_steps/mcp_env_validator/validation_gate_aggregator.py +102 -0
- agent_env/task_step/task_steps/mcp_env_validator/verify_mcp_env_assessment.py +78 -0
- agent_env/task_step/task_steps/mcp_env_validator/verify_mcp_tool_schema.py +152 -0
- agent_env/task_step/task_steps/mcp_env_validator/verify_spec_conformance.py +486 -0
- agent_env/task_step/task_steps/modify_env_tool_access.py +80 -0
- agent_env/task_step/task_steps/multienv_validator/__init__.py +3 -0
- agent_env/task_step/task_steps/multienv_validator/combine_universe_verdicts.py +107 -0
- agent_env/task_step/task_steps/multienv_validator/universe_comparison.py +253 -0
- agent_env/task_step/task_steps/multienv_validator/verify_universe_agent_judge.py +180 -0
- agent_env/task_step/task_steps/multienv_validator/verify_universe_roundtrip.py +241 -0
- agent_env/task_step/task_steps/peer_agents.py +92 -0
- agent_env/task_step/task_steps/prompt_agent.py +900 -0
- agent_env/task_step/task_steps/register_agent_triggers.py +113 -0
- agent_env/task_step/task_steps/register_env_triggers.py +97 -0
- agent_env/task_step/task_steps/reset_env.py +71 -0
- agent_env/task_step/task_steps/review.py +152 -0
- agent_env/task_step/task_steps/run_code.py +461 -0
- agent_env/task_step/task_steps/run_code_runner.py +52 -0
- agent_env/task_step/task_steps/run_docker_container.py +380 -0
- agent_env/task_step/task_steps/sandbox_utils/__init__.py +0 -0
- agent_env/task_step/task_steps/sandbox_utils/sandbox_utils.py +79 -0
- agent_env/task_step/task_steps/snapshot_agent_state.py +205 -0
- agent_env/task_step/task_steps/snapshot_env.py +637 -0
- agent_env/task_step/task_steps/sync_env_clock.py +132 -0
- agent_env/task_step/task_steps/teardown_sandboxes.py +170 -0
- agent_env/task_step/task_steps/verifiers/__init__.py +23 -0
- agent_env/task_step/task_steps/verifiers/agent_prompt_response_verifier.py +166 -0
- agent_env/task_step/task_steps/verifiers/aggregate_verifiers.py +90 -0
- agent_env/task_step/task_steps/verifiers/env_outcome_verifier.py +127 -0
- agent_env/task_step/task_steps/verifiers/judge_utils/__init__.py +2 -0
- agent_env/task_step/task_steps/verifiers/judge_utils/frame_selection.py +377 -0
- agent_env/task_step/task_steps/verifiers/judge_utils/judge_output_format.py +1293 -0
- agent_env/task_step/task_steps/verifiers/judge_utils/trajectory_filter.py +514 -0
- agent_env/task_step/task_steps/verifiers/rubrics_verifier.py +1318 -0
- agent_env/task_step/task_steps/verifiers/run_container_unit_tests_verifier.py +434 -0
- agent_env/task_step/task_steps/verifiers/scoring.py +40 -0
- agent_env/task_step/task_steps/verifiers/verify_sandbox.py +308 -0
- agent_env/task_step/thread_work.py +61 -0
- agent_env/utils/__init__.py +0 -0
- agent_env/utils/card_naming.py +108 -0
- agent_env/utils/deprecation.py +109 -0
- agent_env/utils/docker_build.py +29 -0
- agent_env/utils/exec_retry.py +77 -0
- agent_env/utils/litellm_attribution.py +59 -0
- agent_env/utils/paths.py +20 -0
- agentenv_framework-0.9.1254.dist-info/METADATA +394 -0
- agentenv_framework-0.9.1254.dist-info/RECORD +312 -0
- agentenv_framework-0.9.1254.dist-info/WHEEL +4 -0
- agentenv_framework-0.9.1254.dist-info/entry_points.txt +5 -0
- agentenv_framework-0.9.1254.dist-info/licenses/LICENSE +202 -0
- agentenv_framework-0.9.1254.dist-info/licenses/NOTICE +4 -0
- agentenv_framework-0.9.1254.dist-info/licenses/THIRD_PARTY_NOTICES.md +7665 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Task module for AgentEnv."""
|
|
2
|
+
|
|
3
|
+
from .task import Task
|
|
4
|
+
from .store import (
|
|
5
|
+
TaskStore, TaskQuery, get_task_store, set_task_store, reset_task_store,
|
|
6
|
+
TaskInstance, TaskInstanceStore, get_task_instance_store, set_task_instance_store, reset_task_instance_store,
|
|
7
|
+
TaskStepResult, TaskStepStatus, record_step_complete, record_task_cancelled, record_task_failure,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"Task",
|
|
12
|
+
"TaskStore",
|
|
13
|
+
"TaskQuery",
|
|
14
|
+
"get_task_store",
|
|
15
|
+
"set_task_store",
|
|
16
|
+
"reset_task_store",
|
|
17
|
+
"TaskInstance",
|
|
18
|
+
"TaskInstanceStore",
|
|
19
|
+
"TaskStepResult",
|
|
20
|
+
"TaskStepStatus",
|
|
21
|
+
"get_task_instance_store",
|
|
22
|
+
"set_task_instance_store",
|
|
23
|
+
"reset_task_instance_store",
|
|
24
|
+
"record_step_complete",
|
|
25
|
+
"record_task_cancelled",
|
|
26
|
+
"record_task_failure",
|
|
27
|
+
]
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""Ctrl-C and SIGTERM for a command that tears down what its runs deployed.
|
|
2
|
+
|
|
3
|
+
``Interrupts`` counts both signals from when it is entered until it exits: inside its event loops and in the
|
|
4
|
+
code between them, so none is lost at a hand-off. Inside ``gather``, the first signal cancels the runs that
|
|
5
|
+
haven't begun their teardown and a second cancels the rest, stopping the teardown. It takes over the process's
|
|
6
|
+
handlers, so it has an effect only in the main thread, and a signal the process inherited as ignored stays
|
|
7
|
+
ignored.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import asyncio
|
|
13
|
+
import signal
|
|
14
|
+
import threading
|
|
15
|
+
import weakref
|
|
16
|
+
from collections.abc import Awaitable, Callable, Coroutine, Iterable, Iterator
|
|
17
|
+
from contextlib import contextmanager
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
SIGNALS = (signal.SIGINT, signal.SIGTERM)
|
|
21
|
+
|
|
22
|
+
_tearing_down: weakref.WeakSet[asyncio.Task] = weakref.WeakSet()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def tearing_down() -> None:
|
|
26
|
+
"""Spare the current task from the first signal: it is tearing down what it deployed."""
|
|
27
|
+
task = asyncio.current_task()
|
|
28
|
+
if task is not None:
|
|
29
|
+
_tearing_down.add(task)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class Interrupts:
|
|
33
|
+
def __init__(self) -> None:
|
|
34
|
+
self.signum: int | None = None # the first signal
|
|
35
|
+
self.count = 0
|
|
36
|
+
self._previous: dict[int, Any] = {}
|
|
37
|
+
self._loop: asyncio.AbstractEventLoop | None = None
|
|
38
|
+
self._on_signal: Callable[[int], None] | None = None
|
|
39
|
+
|
|
40
|
+
def __enter__(self) -> Interrupts:
|
|
41
|
+
if threading.current_thread() is threading.main_thread():
|
|
42
|
+
for signum in SIGNALS:
|
|
43
|
+
if signal.getsignal(signum) is not signal.SIG_IGN:
|
|
44
|
+
self._previous[signum] = signal.signal(signum, self._record)
|
|
45
|
+
return self
|
|
46
|
+
|
|
47
|
+
def __exit__(self, *exc) -> None:
|
|
48
|
+
for signum, previous in self._previous.items():
|
|
49
|
+
signal.signal(signum, signal.SIG_DFL if previous is None else previous)
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def reason(self) -> str:
|
|
53
|
+
return "cancelled by Ctrl-C" if self.signum in (None, signal.SIGINT) else (
|
|
54
|
+
f"cancelled by {signal.Signals(self.signum).name}")
|
|
55
|
+
|
|
56
|
+
def run(self, main: Coroutine) -> Any:
|
|
57
|
+
"""``asyncio.run(main)``, taking the handlers back from the loop once it closes."""
|
|
58
|
+
try:
|
|
59
|
+
return asyncio.run(main)
|
|
60
|
+
finally:
|
|
61
|
+
for signum in self._previous:
|
|
62
|
+
signal.signal(signum, self._record)
|
|
63
|
+
|
|
64
|
+
async def gather(self, runs: Iterable[Awaitable], on_signal: Callable[[int], None]) -> list[asyncio.Future]:
|
|
65
|
+
"""Run ``runs`` at once until each is done, and return them. Each signal calls ``on_signal`` with the
|
|
66
|
+
count so far; the first cancels the runs not tearing down, and every later one cancels them all."""
|
|
67
|
+
futures = [asyncio.ensure_future(run) for run in runs]
|
|
68
|
+
|
|
69
|
+
def cancel(count: int) -> None:
|
|
70
|
+
on_signal(count)
|
|
71
|
+
for future in futures:
|
|
72
|
+
if count > 1 or future not in _tearing_down:
|
|
73
|
+
future.cancel()
|
|
74
|
+
|
|
75
|
+
with self._in_loop(cancel):
|
|
76
|
+
await asyncio.gather(*futures, return_exceptions=True)
|
|
77
|
+
return futures
|
|
78
|
+
|
|
79
|
+
async def stopping(self, runs: Iterable[Awaitable]) -> list:
|
|
80
|
+
"""Await ``runs`` at once; any signal cancels them all."""
|
|
81
|
+
futures = [asyncio.ensure_future(run) for run in runs]
|
|
82
|
+
with self._in_loop(lambda count: [future.cancel() for future in futures]):
|
|
83
|
+
return list(await asyncio.gather(*futures))
|
|
84
|
+
|
|
85
|
+
@contextmanager
|
|
86
|
+
def _in_loop(self, on_signal: Callable[[int], None]) -> Iterator[None]:
|
|
87
|
+
"""Pass each signal to ``on_signal`` in the running loop. The loop's own handlers wake it from any
|
|
88
|
+
thread, and they stay until it closes, counting what comes after, rather than dropping a signal that
|
|
89
|
+
is already on its way."""
|
|
90
|
+
loop = asyncio.get_running_loop()
|
|
91
|
+
self._loop, self._on_signal = loop, on_signal
|
|
92
|
+
for signum in self._previous:
|
|
93
|
+
try:
|
|
94
|
+
loop.add_signal_handler(signum, self._record, signum)
|
|
95
|
+
except (NotImplementedError, RuntimeError):
|
|
96
|
+
pass # the process's handler reaches the loop instead
|
|
97
|
+
for count in range(1, self.count + 1):
|
|
98
|
+
loop.call_soon(on_signal, count)
|
|
99
|
+
try:
|
|
100
|
+
yield
|
|
101
|
+
finally:
|
|
102
|
+
self._loop = self._on_signal = None
|
|
103
|
+
|
|
104
|
+
def _record(self, signum: int, frame=None) -> None:
|
|
105
|
+
self.count += 1
|
|
106
|
+
if self.signum is None:
|
|
107
|
+
self.signum = signum
|
|
108
|
+
if self._on_signal is not None:
|
|
109
|
+
self._loop.call_soon_threadsafe(self._on_signal, self.count)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Task registry for type-based deserialization."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from agent_env.task.task import Task
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _get_type(cls: type["Task"]) -> str:
|
|
12
|
+
return cls.type
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
_registry: dict[str, type["Task"]] | None = None
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def get_task_registry() -> dict[str, type["Task"]]:
|
|
19
|
+
"""Get the task registry, lazily building it to avoid circular imports."""
|
|
20
|
+
global _registry
|
|
21
|
+
if _registry is None:
|
|
22
|
+
from agent_env.task.task import Task
|
|
23
|
+
|
|
24
|
+
_registry = {
|
|
25
|
+
_get_type(Task): Task,
|
|
26
|
+
}
|
|
27
|
+
return _registry
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""The step journal's replay rules: how one step's diff is applied, in what order, and what
|
|
2
|
+
that reconstructs. Pure — the store owns the collection, the sequence, and the CAS."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import dataclasses
|
|
7
|
+
from typing import Iterable
|
|
8
|
+
|
|
9
|
+
from agent_env.task_step.context import TaskStepContext
|
|
10
|
+
from agent_env.task_step.context_ops import ContextUpdateOps
|
|
11
|
+
|
|
12
|
+
_SEED_STEP_ID = "__seed__"
|
|
13
|
+
# The scheduler's own record (retry audit), written by ``undo_steps_sync`` in the same CAS as
|
|
14
|
+
# the undo and replayed right after the seed, so a retry's bookkeeping survives a later undo.
|
|
15
|
+
_SCHEDULER_STEP_ID = "__scheduler__"
|
|
16
|
+
_RESERVED_STEP_IDS = frozenset({_SEED_STEP_ID, _SCHEDULER_STEP_ID})
|
|
17
|
+
|
|
18
|
+
def _get_path(doc: dict, dotted: str):
|
|
19
|
+
"""Read a dotted path (e.g. 'context.metadata.x') from a doc, or None."""
|
|
20
|
+
cur: object = doc
|
|
21
|
+
for part in dotted.split("."):
|
|
22
|
+
if not isinstance(cur, dict):
|
|
23
|
+
return None
|
|
24
|
+
cur = cur.get(part)
|
|
25
|
+
return cur
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _union(existing: list, items: list, *, move_to_end: bool = False, path: str = "") -> list:
|
|
29
|
+
"""Union by value (deployed envs by instance), order-stable. ``move_to_end`` is the re-record case: that step's
|
|
30
|
+
completion moves to the end of the replay order, so its items must move with it."""
|
|
31
|
+
key = _item_key(path)
|
|
32
|
+
fresh = [x for i, x in enumerate(items) if key(x) not in [key(y) for y in items[:i]]]
|
|
33
|
+
fresh_keys = [key(x) for x in fresh]
|
|
34
|
+
if move_to_end:
|
|
35
|
+
return [x for x in existing if key(x) not in fresh_keys] + fresh
|
|
36
|
+
existing_keys = [key(x) for x in existing]
|
|
37
|
+
return list(existing) + [x for x, k in zip(fresh, fresh_keys) if k not in existing_keys]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _set_path(doc: dict, dotted: str, value) -> None:
|
|
41
|
+
cur = doc
|
|
42
|
+
parts = dotted.split(".")
|
|
43
|
+
for part in parts[:-1]:
|
|
44
|
+
nxt = cur.get(part)
|
|
45
|
+
if not isinstance(nxt, dict):
|
|
46
|
+
nxt = {}
|
|
47
|
+
cur[part] = nxt
|
|
48
|
+
cur = nxt
|
|
49
|
+
cur[parts[-1]] = value
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _unset_path(doc: dict, dotted: str) -> None:
|
|
53
|
+
cur = doc
|
|
54
|
+
parts = dotted.split(".")
|
|
55
|
+
for part in parts[:-1]:
|
|
56
|
+
cur = cur.get(part)
|
|
57
|
+
if not isinstance(cur, dict):
|
|
58
|
+
return
|
|
59
|
+
cur.pop(parts[-1], None)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _apply_forward(doc: dict, ops: ContextUpdateOps, *, move_to_end: bool = False) -> None:
|
|
63
|
+
"""Apply one journaled diff in place with the completion write's semantics."""
|
|
64
|
+
for path, value in ops.sets.items():
|
|
65
|
+
_set_path(doc, path, value)
|
|
66
|
+
for path in ops.unsets:
|
|
67
|
+
_unset_path(doc, path)
|
|
68
|
+
for path, items in ops.add_to_sets.items():
|
|
69
|
+
_set_path(doc, path, _union(_get_path(doc, path) or [], items, move_to_end=move_to_end, path=path))
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def commit_ordered(entries: list[dict], completed_steps: list[dict]) -> list[dict]:
|
|
73
|
+
"""Entries in commit order: seed first, then the scheduler's record, then by position in
|
|
74
|
+
``completed_steps``, which the CAS appends and so outranks ``seq``. A step with no
|
|
75
|
+
completion never landed; drop it."""
|
|
76
|
+
pos = {c.get("step_id"): i for i, c in enumerate(completed_steps)}
|
|
77
|
+
pos[_SCHEDULER_STEP_ID] = -1
|
|
78
|
+
pos[_SEED_STEP_ID] = -2
|
|
79
|
+
kept = [e for e in entries if e["step_id"] in pos]
|
|
80
|
+
return sorted(kept, key=lambda e: pos[e["step_id"]])
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def replay_context(entries: list[dict], rerecorded_steps: Iterable[str]) -> dict:
|
|
84
|
+
"""The context ``entries`` produce over an empty one; order them with ``commit_ordered``.
|
|
85
|
+
``rerecorded_steps`` is the instance's own list, the only record of which steps re-recorded:
|
|
86
|
+
a flag on the entry would be a pre-CAS read and can disagree with the write that landed."""
|
|
87
|
+
flagged = set(rerecorded_steps)
|
|
88
|
+
doc = {"context": dataclasses.asdict(TaskStepContext())}
|
|
89
|
+
for e in entries:
|
|
90
|
+
ops = ContextUpdateOps.from_journal_dict(e["ops"])
|
|
91
|
+
_apply_forward(doc, ops, move_to_end=e["step_id"] in flagged)
|
|
92
|
+
return doc["context"]
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _item_key(path: str):
|
|
96
|
+
"""How a context list's items are told apart: a deployed env by its instance, so two kernels' copies stay one entry."""
|
|
97
|
+
if path == "context.deployed_envs":
|
|
98
|
+
return lambda item: (item.get("instance_id") if isinstance(item, dict) else None) or item
|
|
99
|
+
return lambda item: item
|