diff --git a/README.md b/README.md
index e4d336d..c1c7b8f 100644
--- a/README.md
+++ b/README.md
@@ -143,6 +143,25 @@ workers run in isolated containers, never push to git directly, and upload `patc
set it up with [`/setup-agystack`](./skills/setup-agystack/SKILL.md) with your Google Cloud auth and run [`/swarm`](./skills/swarm/SKILL.md) to dispatch.
+### summoning cloud agents (Gemini & DeepSeek)
+
+Cloud swarms run on Google Cloud Run with zero-friction serverless models:
+
+```bash
+# list supported cloud models and tiers
+python3 skills/swarm/scripts/cloud_dispatch.py --list-models
+
+# fan out 5 Gemini Argon agents
+python3 skills/swarm/scripts/cloud_dispatch.py --tasks 5 --model gemini-4-argon
+
+# fan out 5 DeepSeek Flash 4.1 agents
+python3 skills/swarm/scripts/cloud_dispatch.py --tasks 5 --model deepseek-v4.1-flash
+```
+
+Or trigger `/swarm` right in chat:
+- `/swarm 5 workers using Gemini Argon to test src/`
+- `/swarm 5 workers using DeepSeek Flash to explore hypotheses`
+
## skills
[`/poteto-mode`](./skills/poteto-mode/SKILL.md) runs most of these for you when a step needs them (`how`, `why`, `architect`, `arena`, `swarm`, `interrogate`, `unslop`, `no-comments`, `technical-writing`, `tdd`, and the principles). the table below is for when you want one directly:
diff --git a/rules/AGENTS.md b/rules/AGENTS.md
index cdef34c..92b2151 100644
--- a/rules/AGENTS.md
+++ b/rules/AGENTS.md
@@ -31,6 +31,10 @@ Mirroring pstack in Cursor, enforce strict separation between coordination and c
- **Interactive State-Changing Playbooks (`Feature`, `Refactoring`, `Multi-phase`):** Mandatory `implementation_plan.md` with `RequestFeedback: true` before coding; mandatory `walkthrough.md` with pass receipts upon completion.
- **Autonomous & Hillclimb Playbooks (`Autonomous run`, `Hillclimb`, `Swarm`, `/goal`, `/loop`):** Unattended throughput invariant. Use `RequestFeedback: false`. Domain deliverables (`decision_trail.md`, `score.json`, `receipts/`) take precedence without interactive blocking.
- **Read-Only Investigation Playbooks (`Investigation`, `How`, `Why`, `Runtime Forensics`):** Produces `investigation_report.md` with `RequestFeedback: false`.
+6. **Cloud Swarm Model Selection & Zero-Friction Fallback:**
+ - **Prefer Tier 1 Serverless Models:** Default to frictionless serverless models that require zero console forms: `gemini-3.8-flash`, `gemini-4-argon`, or `deepseek-v4.1-flash` (MaaS).
+ - **Gated Partner Gating Detection:** If a user requests an Anthropic partner model (`claude-3-opus`, `claude-3-5-sonnet`) that is not yet enabled in Vertex AI Model Garden, explain that Google enforces a one-time console form click-through that agents cannot accept programmatically, and immediately offer the Tier 1 zero-friction alternative.
+ - **Open-Weights vs. MaaS Distinction:** Models like `GLM 5.3` or `Llama 3.3` on Model Garden are deployable open-weights requiring dedicated GPU endpoints (8x H100/B200 instances), not on-demand serverless APIs. Never attempt to dispatch Cloud Run tasks against deploy-only models without an active dedicated endpoint.
## Surface Precedence Hierarchy
diff --git a/skills/setup-agystack/SKILL.md b/skills/setup-agystack/SKILL.md
index 0cea412..4ff7704 100644
--- a/skills/setup-agystack/SKILL.md
+++ b/skills/setup-agystack/SKILL.md
@@ -54,12 +54,19 @@ Do not leave the user to do the work. Complete the deployment end-to-end for the
### 2. Detect available models
-Enumerate the model tiers you can pass to `invoke_subagent`:
+Enumerate the model tiers you can pass to `invoke_subagent` (local) and `cloud_dispatch.py` (Cloud Run):
+
+**Local subagent tiers (`invoke_subagent`):**
- `pro`: High-capability tier (maximum reasoning budget for complex code, architecture, and hard tasks)
- `flash`: Balanced fast tier (fast execution for exploration and standard generation)
- `flash_lite`: Lightweight tier (minimal latency for quick lookups)
- `inherit` (or `auto`): Inherit parent chat model
+**Cloud Run swarm model tiers (`cloud_dispatch.py`):**
+- **Tier 1 (Zero-Friction MaaS):** `gemini-4-argon`, `gemini-3.8-flash`, `deepseek-v4.1-flash` (Instant on global Vertex endpoint, zero forms).
+- **Tier 2 (Gated Partner MaaS):** `claude-3-7-sonnet`, `claude-3-5-sonnet`, `claude-3-opus` (Requires one-time Console agreement).
+- **Tier 3 (Dedicated GPU Endpoint):** Open-weights models like `glm-5-3-flash` requiring dedicated 8x H100/B200 GPU instances.
+
### 3. Load current state
If `~/.gemini/config/plugins/agystack/rules/agystack-models.md` exists, read its current role assignments. Otherwise start from skill defaults.
diff --git a/skills/setup-agystack/scripts/setup_runtime.py b/skills/setup-agystack/scripts/setup_runtime.py
index c0f9701..9695087 100644
--- a/skills/setup-agystack/scripts/setup_runtime.py
+++ b/skills/setup-agystack/scripts/setup_runtime.py
@@ -115,6 +115,12 @@ def check_model_garden(project_id=None, region="us-central1", run_cmd_fn=None, i
"project_id": project,
"region": region,
"anthropic_installed": False,
+ "serverless_models": [
+ "gemini-4-argon",
+ "gemini-3.8-flash",
+ "gemini-3.1-pro",
+ "deepseek-v4.1-flash-maas",
+ ],
"partner_models": [
"claude-3-7-sonnet@20250219",
"claude-3-5-sonnet-v2@20241022",
@@ -140,9 +146,8 @@ def check_model_garden(project_id=None, region="us-central1", run_cmd_fn=None, i
result["error"] = f"Failed to initialize AnthropicVertex client: {init_exc}"
return result
except ImportError:
- result["ok"] = False
- result["error"] = "Package 'anthropic[vertex]' is not installed (run: pip install anthropic[vertex])"
- return result
+ result["anthropic_installed"] = False
+ result["partner_models_note"] = "Optional package 'anthropic[vertex]' not installed for Claude partner models."
except Exception as exc:
result["ok"] = False
result["error"] = f"Error importing 'anthropic': {exc}"
@@ -576,6 +581,17 @@ def create_project(project_id):
run_cmd(["gcloud", "projects", "create", project_id])
print("Setting active project...")
run_cmd(["gcloud", "config", "set", "project", project_id])
+ try:
+ res = run_cmd(["gcloud", "billing", "accounts", "list", "--format=json"], check=False)
+ if getattr(res, "returncode", 1) == 0:
+ accounts = json.loads(getattr(res, "stdout", "[]"))
+ open_accounts = [a for a in accounts if a.get("open", False)]
+ if open_accounts:
+ billing_id = open_accounts[0].get("name", "").split("/")[-1]
+ print(f"Linking billing account {billing_id} to project {project_id}...")
+ run_cmd(["gcloud", "billing", "projects", "link", project_id, f"--billing-account={billing_id}"], check=False)
+ except Exception:
+ pass
def enable_apis(project_id):
print(f"Enabling APIs for {project_id}...")
@@ -711,7 +727,7 @@ def _ignore_copy(directory, contents):
ignored.add(item)
continue
resolved_item = (dir_p / item).resolve()
- if resolved_item == dst_resolved or resolved_item == scripts_path:
+ if resolved_item == dst_resolved or resolved_item == scripts_path or scripts_path in resolved_item.parents or resolved_item in scripts_path.parents:
ignored.add(item)
return ignored
@@ -723,7 +739,8 @@ def _ignore_copy(directory, contents):
"gcloud", "builds", "submit",
f"--tag={image_tag}",
scripts_dir,
- f"--project={project_id}"
+ f"--project={project_id}",
+ f"--region={region}"
], capture_output=False)
finally:
for staged in staged_dirs:
diff --git a/skills/swarm/SKILL.md b/skills/swarm/SKILL.md
index cde1519..8ee2dae 100644
--- a/skills/swarm/SKILL.md
+++ b/skills/swarm/SKILL.md
@@ -42,6 +42,16 @@ Spawn all N workers in one `invoke_subagent` call with `TypeName: "poteto-agent"
Cloud Run swarms enable parallel execution across dozens or hundreds of container tasks.
+**Model Selection & Friction Tiers:**
+
+Before launching cloud swarms, match the model to the project's Model Garden enablement status:
+
+| Tier | Models | Endpoint Type | Friction | Setup Required |
+|---|---|---|---|---|
+| **Tier 1: Native MaaS** | `gemini-4-argon`, `gemini-3.8-flash`, `deepseek-v4.1-flash` | Global Serverless | **Zero** | Instant via existing ADC / project IAM |
+| **Tier 2: Gated Partner MaaS** | `claude-3-7-sonnet`, `claude-3-5-sonnet`, `claude-3-opus` | Regional Serverless | **Low / One-Time** | Console agreement click-through required |
+| **Tier 3: Dedicated GPU** | `glm-5-3-flash`, `llama-3.3-70b` | Dedicated Endpoint | **High** | Dedicated 8x H100/B200 GPU instance provisioning |
+
**Quota & Auth Prerequisites:**
- **Paid Tier API Key or Vertex AI:** Cloud Run swarms require either a paid Google AI Studio tier (Pay-as-you-go / Tier 1+) or verified Vertex AI permissions (`roles/aiplatform.user` on the target project). Free-tier API keys (5 RPM) are prohibited as parallel container workers will immediately exhaust quota and fail.
- **Git Authentication:** A valid GitHub token (`gh auth token` or `GH_TOKEN` / `GITHUB_TOKEN`) with repository clone access. Under the zero-push architecture, cloud containers do not require git push access.
diff --git a/skills/swarm/scripts/cloud_dispatch.py b/skills/swarm/scripts/cloud_dispatch.py
index 77e9b6b..76a6c80 100644
--- a/skills/swarm/scripts/cloud_dispatch.py
+++ b/skills/swarm/scripts/cloud_dispatch.py
@@ -23,6 +23,33 @@
except ImportError:
StorageMessenger = None
+try:
+ from worker.partner_engine import PARTNER_MODEL_ALIASES, is_partner_model
+except ImportError:
+ try:
+ from skills.swarm.scripts.worker.partner_engine import PARTNER_MODEL_ALIASES, is_partner_model
+ except ImportError:
+ is_partner_model = None
+ PARTNER_MODEL_ALIASES = {}
+
+TIER3_DEDICATED_MODELS = {
+ "glm-5-3-flash",
+ "glm-5.3-flash",
+ "glm-5.3",
+ "llama-3.3-70b",
+ "llama-3.3-70b-instruct",
+ "llama-3.3",
+}
+DEEPSEEK_CANONICAL_ALIASES = {
+ "deepseek",
+ "deepseek-flash",
+ "deepseek-4.1",
+ "deepseek-v4.1",
+ "deepseek-4.1-flash",
+ "deepseek-v4.1-flash-maas",
+ "deepseek-v4.1-flash-maas@001",
+}
+
def find_runtime_config() -> Optional[Dict[str, Any]]:
paths_to_check = [
@@ -800,6 +827,14 @@ def run_preflight(
) -> None:
print("[PRE-FLIGHT] Verifying cloud swarm credentials, quota tiers, repository access, and GCS bucket...", flush=True)
+ # Tier 3 Dedicated Model Interception
+ if model and (model.lower().strip() in TIER3_DEDICATED_MODELS or model.lower().strip().replace(".", "-").replace("-instruct", "") in TIER3_DEDICATED_MODELS):
+ raise RuntimeError(
+ f"Model '{model}' is a Tier 3 open-weights model requiring a dedicated GPU cluster endpoint.\n"
+ "Serverless Cloud Run swarms cannot dispatch directly to deploy-only models without an active dedicated endpoint.\n"
+ "Recommended Tier 1 zero-friction alternatives: gemini-4-argon, gemini-3.8-flash, deepseek-v4.1-flash."
+ )
+
if not dry_run and runtime == "cloud-run" and not gcs_bucket:
raise RuntimeError(
"Cloud Run swarms require a GCS bucket to store and deliver candidate patches under the zero-push architecture.\n"
@@ -807,7 +842,7 @@ def run_preflight(
)
if not vertex_location:
- vertex_location = "global" if model.startswith(("gemini-2.5", "gemini-3")) else region
+ vertex_location = "global" if (model and (model.startswith(("gemini-2.5", "gemini-3", "gemini-4")) or "deepseek" in model.lower())) else region
# 1. Git Authentication & Repository Accessibility Check
if not repo_url:
@@ -853,7 +888,10 @@ def run_preflight(
except Exception as exc:
raise RuntimeError(f"Git remote accessibility error: {exc}") from exc
- # 2. Model & Quota Tier Check
+ # 2. Model & Quota Tier Check: Auto-promote Vertex for Partner models and DeepSeek
+ if (is_partner_model and is_partner_model(model)) or (model and "deepseek" in model.lower()):
+ use_vertex = True
+
if use_vertex:
token_cmd = subprocess.run(
["gcloud", "auth", "print-access-token"],
@@ -875,14 +913,43 @@ def run_preflight(
"GCP project ID is required for Vertex AI mode (--project or agystack-runtime.json)."
)
- if vertex_location == "global":
+ target_loc = vertex_location or "global"
+ resolved_model = model
+
+ if is_partner_model and is_partner_model(model) and "fable" not in model.lower():
+ resolved_model = PARTNER_MODEL_ALIASES.get(model.lower(), model)
+ target_loc = vertex_location if vertex_location and vertex_location != "global" else (region if region and region != "global" else "us-east5")
+ probe_url = f"https://{target_loc}-aiplatform.googleapis.com/v1/projects/{project}/locations/{target_loc}/publishers/anthropic/models/{resolved_model}:rawPredict"
+ req_data = json.dumps({
+ "anthropic_version": "vertex-2023-10-16",
+ "messages": [{"role": "user", "content": "ping"}],
+ "max_tokens": 1,
+ }).encode("utf-8")
+ elif model and "deepseek" in model.lower():
+ target_loc = vertex_location or "global"
+ if model.startswith("publishers/"):
+ resolved_model = model.split("/")[-1]
+ probe_url = f"https://aiplatform.googleapis.com/v1/projects/{project}/locations/{target_loc}/{model}:generateContent"
+ else:
+ # Canonicalize aliases like 'deepseek', 'deepseek-flash', 'deepseek-4.1', 'deepseek-v4.1', 'deepseek-v4.1-flash'
+ resolved_model = "deepseek-v4.1-flash-maas"
+ probe_url = f"https://aiplatform.googleapis.com/v1/projects/{project}/locations/{target_loc}/publishers/deepseek-ai/models/{resolved_model}:generateContent"
+ req_data = json.dumps({
+ "contents": [{"role": "user", "parts": [{"text": "ping"}]}],
+ "generationConfig": {"maxOutputTokens": 1},
+ }).encode("utf-8")
+ elif target_loc == "global":
probe_url = f"https://aiplatform.googleapis.com/v1/projects/{project}/locations/global/publishers/google/models/{model}:generateContent"
+ req_data = json.dumps({
+ "contents": [{"role": "user", "parts": [{"text": "ping"}]}],
+ "generationConfig": {"maxOutputTokens": 1},
+ }).encode("utf-8")
else:
- probe_url = f"https://{vertex_location}-aiplatform.googleapis.com/v1/projects/{project}/locations/{vertex_location}/publishers/google/models/{model}:generateContent"
- req_data = json.dumps({
- "contents": [{"role": "user", "parts": [{"text": "ping"}]}],
- "generationConfig": {"maxOutputTokens": 1},
- }).encode("utf-8")
+ probe_url = f"https://{target_loc}-aiplatform.googleapis.com/v1/projects/{project}/locations/{target_loc}/publishers/google/models/{model}:generateContent"
+ req_data = json.dumps({
+ "contents": [{"role": "user", "parts": [{"text": "ping"}]}],
+ "generationConfig": {"maxOutputTokens": 1},
+ }).encode("utf-8")
headers = {
"Authorization": f"Bearer {access_token}",
@@ -896,14 +963,29 @@ def run_preflight(
method="POST",
)
try:
- with urllib.request.urlopen(req, timeout=15) as resp:
+ with urllib.request.urlopen(req, timeout=30) as resp:
pass
- print(f" [PASS] Vertex AI credentials and publisher model '{model}' verified in {vertex_location} (project: {project}).")
+ if is_partner_model and is_partner_model(model) and "fable" not in model.lower():
+ print(f" [PASS] Vertex AI credentials and Model Garden partner model '{model}' ({resolved_model}) verified in {target_loc} (project: {project}).")
+ elif model and "deepseek" in model.lower():
+ print(f" [PASS] Vertex AI credentials and DeepSeek MaaS model '{resolved_model}' verified in {target_loc} (project: {project}).")
+ else:
+ print(f" [PASS] Vertex AI credentials and publisher model '{model}' verified in {target_loc} (project: {project}).")
except urllib.error.HTTPError as exc:
err_body = exc.read().decode("utf-8", errors="replace")
- if exc.code == 403:
+ if is_partner_model and is_partner_model(model) and "fable" not in model.lower() and exc.code == 404:
+ raise RuntimeError(
+ f"Vertex AI Model Garden partner model '{model}' (resolved to '{resolved_model}') not found or not enabled in location '{target_loc}' (404 Not Found).\n"
+ f"Server Response:\n{err_body}\n"
+ "Guidance to enable partner models in Vertex AI:\n"
+ "1. Open Google Cloud Console -> Vertex AI -> Model Garden.\n"
+ f"2. Search for the model ('{model}' or '{resolved_model}').\n"
+ "3. Click 'Enable' to accept the Model Garden terms for this project.\n"
+ f"4. Verify availability in region/location '{target_loc}'."
+ ) from exc
+ elif exc.code == 403:
raise RuntimeError(
- f"Vertex AI probe returned 403 Forbidden for project '{project}' in location '{vertex_location}'.\n"
+ f"Vertex AI probe returned 403 Forbidden for project '{project}' in location '{target_loc}'.\n"
f"Server Response:\n{err_body}\n"
"Required Permissions:\n"
"- Ensure active principal has the 'Vertex AI User' role (roles/aiplatform.user).\n"
@@ -911,13 +993,13 @@ def run_preflight(
) from exc
elif exc.code == 404:
raise RuntimeError(
- f"Vertex AI publisher model '{model}' not found in location '{vertex_location}' (404 Not Found).\n"
+ f"Vertex AI publisher model '{model}' (resolved to '{resolved_model}') not found in location '{target_loc}' (404 Not Found).\n"
f"Server Response:\n{err_body}\n"
- f"Verify that model '{model}' is supported and published in location '{vertex_location}'."
+ f"Verify that model '{model}' is supported and published in location '{target_loc}'."
) from exc
else:
raise RuntimeError(
- f"Vertex AI probe failed with HTTP {exc.code} for project '{project}' in location '{vertex_location}':\n{err_body}"
+ f"Vertex AI probe failed with HTTP {exc.code} for project '{project}' in location '{target_loc}':\n{err_body}"
) from exc
except Exception as exc:
if isinstance(exc, RuntimeError):
@@ -1045,6 +1127,16 @@ def resolve_swarm_model(
"pro": "gemini-3.1-pro",
"flash_lite": "gemini-3.1-flash-lite",
"flash-lite": "gemini-3.1-flash-lite",
+ "argon": "gemini-4-argon",
+ "gemini-argon": "gemini-4-argon",
+ "gemini-4": "gemini-4-argon",
+ "gemini": "gemini-3.8-flash",
+ "deepseek": "deepseek-v4.1-flash",
+ "deepseek-flash": "deepseek-v4.1-flash",
+ "deepseek-4.1": "deepseek-v4.1-flash",
+ "deepseek-v4.1": "deepseek-v4.1-flash",
+ "deepseek-4.1-flash": "deepseek-v4.1-flash",
+ "deepseek-v4.1-flash-maas": "deepseek-v4.1-flash",
"inherit": default_model,
"auto": default_model,
"inherit-parent": default_model,
@@ -1179,6 +1271,7 @@ def main() -> None:
parser.add_argument("--base-branch", type=str, default=None, help="Base branch for workers to checkout (default: remote default branch).")
parser.add_argument("--conversation-id", type=str, default=None, help="Coordinator conversation ID for swarm dashboard artifact.")
parser.add_argument("--ignore-partial-failures", action="store_true", help="Allow promotion of candidate patches from partially-failed swarm executions.")
+ parser.add_argument("--list-models", action="store_true", help="List supported cloud swarm models and their Model Garden friction tiers.")
parser.set_defaults(wait=True)
subparsers = parser.add_subparsers(dest="subcommand", help="Optional subcommand")
@@ -1215,6 +1308,28 @@ def main() -> None:
args = parser.parse_args()
+ if getattr(args, "list_models", False):
+ print("""
+Vertex AI Model Garden Tiers for Cloud Swarms:
+-----------------------------------------------------------------------------------------
+Tier 1: Native MaaS (Instant Serverless, Zero Forms)
+ - gemini-4-argon Global Vertex AI endpoint, deep reasoning
+ - gemini-3.8-flash Global Vertex AI endpoint, balanced speed
+ - gemini-3.1-pro Global Vertex AI endpoint, high capability
+ - deepseek-v4.1-flash Global Vertex AI endpoint (deepseek-ai namespace)
+
+Tier 2: Gated Partner MaaS (One-Time Console Agreement Form Required)
+ - claude-3-7-sonnet Regional endpoint (us-east5/us-central1)
+ - claude-3-5-sonnet Regional endpoint (us-east5/us-central1)
+ - claude-3-opus Regional endpoint (us-east5/us-central1)
+
+Tier 3: Dedicated GPU Cluster Endpoints (Self-Hosted, Not Serverless)
+ - glm-5-3-flash Requires dedicated 8x H100/B200 GPU cluster
+ - llama-3.3-70b Requires dedicated multi-GPU cluster
+-----------------------------------------------------------------------------------------
+""".strip())
+ return
+
if args.subcommand == "steer":
runtime_cfg = find_runtime_config() or {}
session_id = args.session or os.environ.get("SWARM_SESSION_ID") or os.environ.get("SESSION_ID") or "default"
@@ -1259,7 +1374,26 @@ def main() -> None:
runtime_model=runtime_cfg.get("model"),
parent_model=getattr(args, "parent_model", None),
)
- vertex_location = args.vertex_location or runtime_cfg.get("vertex_location") or ("global" if model.startswith(("gemini-2.5", "gemini-3")) else region)
+ explicit_vertex_loc = args.vertex_location or runtime_cfg.get("vertex_location")
+ if explicit_vertex_loc:
+ vertex_location = explicit_vertex_loc
+ elif model and (model.startswith(("gemini-2.5", "gemini-3", "gemini-4")) or "deepseek" in model.lower()):
+ vertex_location = "global"
+ else:
+ vertex_location = region
+
+ if is_partner_model and is_partner_model(model):
+ use_vertex = True
+ if not explicit_vertex_loc and vertex_location == "global":
+ vertex_location = region if region and region != "global" else "us-east5"
+ elif model and "deepseek" in model.lower():
+ use_vertex = True
+ if not explicit_vertex_loc:
+ vertex_location = "global"
+
+ gemini_api_key = os.environ.get("GEMINI_API_KEY", "").strip()
+ if not gemini_api_key and (project or (is_partner_model and is_partner_model(model)) or (model and "deepseek" in model.lower())):
+ use_vertex = True
gcs_bucket = args.gcs_bucket if args.gcs_bucket is not None else runtime_cfg.get("gcs_bucket", "")
if args.harvest_session:
@@ -1505,7 +1639,10 @@ def main() -> None:
env_vars["TASK_MANIFEST"] = manifest_serialized
if model:
- env_vars["MODEL_OVERRIDE"] = model
+ if model.lower().strip() in DEEPSEEK_CANONICAL_ALIASES:
+ env_vars["MODEL_OVERRIDE"] = "publishers/deepseek-ai/models/deepseek-v4.1-flash-maas"
+ else:
+ env_vars["MODEL_OVERRIDE"] = model
dispatch_cmd = build_gcloud_command(
job_name=job_name,
diff --git a/skills/swarm/scripts/cloud_worker.py b/skills/swarm/scripts/cloud_worker.py
index a0feb05..305d59b 100644
--- a/skills/swarm/scripts/cloud_worker.py
+++ b/skills/swarm/scripts/cloud_worker.py
@@ -717,9 +717,11 @@ def evaluate_agent_status(agent_text: str) -> Tuple[str, str]:
PARTNER_MODELS as PARTNER_MODELS,
WORKSPACE_TOOLS as WORKSPACE_TOOLS,
execute_claude_model_agent as execute_claude_model_agent,
+ execute_deepseek_model_agent as execute_deepseek_model_agent,
execute_fable_model_agent as execute_fable_model_agent,
execute_partner_model_agent as execute_partner_model_agent,
execute_workspace_tool as execute_workspace_tool,
+ is_deepseek_model as is_deepseek_model,
is_fable_model as is_fable_model,
is_partner_model as is_partner_model,
measure_partner_client_startup as measure_partner_client_startup,
@@ -730,9 +732,11 @@ def evaluate_agent_status(agent_text: str) -> Tuple[str, str]:
PARTNER_MODELS as PARTNER_MODELS,
WORKSPACE_TOOLS as WORKSPACE_TOOLS,
execute_claude_model_agent as execute_claude_model_agent,
+ execute_deepseek_model_agent as execute_deepseek_model_agent,
execute_fable_model_agent as execute_fable_model_agent,
execute_partner_model_agent as execute_partner_model_agent,
execute_workspace_tool as execute_workspace_tool,
+ is_deepseek_model as is_deepseek_model,
is_fable_model as is_fable_model,
is_partner_model as is_partner_model,
measure_partner_client_startup as measure_partner_client_startup,
@@ -767,6 +771,17 @@ def resolve_worker_model(raw_model: Optional[str] = None, task_item: Any = None)
"pro": "gemini-3.1-pro",
"flash_lite": "gemini-3.1-flash-lite",
"flash-lite": "gemini-3.1-flash-lite",
+ "argon": "gemini-4-argon",
+ "gemini-argon": "gemini-4-argon",
+ "gemini-4": "gemini-4-argon",
+ "gemini": "gemini-3.8-flash",
+ "deepseek": "publishers/deepseek-ai/models/deepseek-v4.1-flash-maas",
+ "deepseek-flash": "publishers/deepseek-ai/models/deepseek-v4.1-flash-maas",
+ "deepseek-4.1": "publishers/deepseek-ai/models/deepseek-v4.1-flash-maas",
+ "deepseek-v4.1": "publishers/deepseek-ai/models/deepseek-v4.1-flash-maas",
+ "deepseek-4.1-flash": "publishers/deepseek-ai/models/deepseek-v4.1-flash-maas",
+ "deepseek-v4.1-flash-maas": "publishers/deepseek-ai/models/deepseek-v4.1-flash-maas",
+ "deepseek-v4.1-flash-maas@001": "publishers/deepseek-ai/models/deepseek-v4.1-flash-maas",
"auto": default_model,
"inherit": default_model,
"inherit-parent": default_model,
@@ -835,6 +850,20 @@ def execute_task(
return "ISSUES", f"Command timed out after {timeout} seconds: {cmd_str}"
resolved_model = resolve_worker_model(raw_model=model_override, task_item=task_item)
+ if is_deepseek_model(resolved_model):
+ location_override = "global" if (not location or location == "us-central1" or not os.environ.get("VERTEXAI_LOCATION")) else location
+ emit_milestone(task_index, "RUNNING_AGENT", f"Vertex Model Garden agent ({resolved_model})")
+ return execute_deepseek_model_agent(
+ model=resolved_model,
+ task_brief=task_brief,
+ repo_dir=repo_dir,
+ task_item=task_item,
+ task_index=task_index,
+ project=project,
+ location=location_override,
+ messenger=messenger,
+ )
+
if is_partner_model(resolved_model):
emit_milestone(task_index, "RUNNING_AGENT", f"Vertex Model Garden agent ({resolved_model})")
return execute_partner_model_agent(
@@ -984,21 +1013,6 @@ def main() -> None:
if not gh_token:
print("Warning: Missing GH_TOKEN. Proceeding with unauthenticated git operations.", file=sys.stderr)
- use_vertex = (
- os.environ.get("USE_VERTEX_AI", "").lower() in ("1", "true", "yes")
- or bool(os.environ.get("VERTEXAI_PROJECT"))
- or bool(os.environ.get("GCP_PROJECT"))
- or is_partner_model(os.environ.get("MODEL_OVERRIDE", ""))
- )
- gemini_api_key = os.environ.get("GEMINI_API_KEY", "").strip()
- if not use_vertex and not gemini_api_key:
- emit_milestone(task_index, "COMPLETE", "BLOCKED")
- print(
- "[STATUS: BLOCKED]\nEvidence: Missing authentication. Neither USE_VERTEX_AI nor GEMINI_API_KEY is provided.\nSummary: Authentication configuration error.",
- flush=True,
- )
- sys.exit(1)
-
project = os.environ.get("VERTEXAI_PROJECT") or os.environ.get("GCP_PROJECT") or os.environ.get("PROJECT_ID")
location = os.environ.get("VERTEXAI_LOCATION") or os.environ.get("GCP_REGION") or os.environ.get("REGION")
model_override = os.environ.get("MODEL_OVERRIDE", "")
@@ -1018,8 +1032,21 @@ def main() -> None:
sys.exit(1)
task_model = resolve_worker_model(raw_model=model_override, task_item=task_item)
- if is_partner_model(task_model):
- use_vertex = True
+ use_vertex = (
+ os.environ.get("USE_VERTEX_AI", "").lower() in ("1", "true", "yes")
+ or bool(os.environ.get("VERTEXAI_PROJECT"))
+ or bool(os.environ.get("GCP_PROJECT"))
+ or is_partner_model(task_model)
+ or is_deepseek_model(task_model)
+ )
+ gemini_api_key = os.environ.get("GEMINI_API_KEY", "").strip()
+ if not use_vertex and not gemini_api_key:
+ emit_milestone(task_index, "COMPLETE", "BLOCKED")
+ print(
+ "[STATUS: BLOCKED]\nEvidence: Missing authentication. Neither USE_VERTEX_AI nor GEMINI_API_KEY is provided.\nSummary: Authentication configuration error.",
+ flush=True,
+ )
+ sys.exit(1)
branch_name = resolve_worker_branch(task_item, task_index)
diff --git a/skills/swarm/scripts/worker/partner_engine.py b/skills/swarm/scripts/worker/partner_engine.py
index 2bdc9fb..b1cedd5 100644
--- a/skills/swarm/scripts/worker/partner_engine.py
+++ b/skills/swarm/scripts/worker/partner_engine.py
@@ -23,6 +23,16 @@
except Exception:
evaluate_safety = None
+DEFAULT_PARTNER_SYSTEM_PROMPT = """You are a specialized poteto coding delegate for agystack executing inside an isolated cloud worker container.
+You implement surgical code edits, run test suites, verify outputs, and keep code and diffs clean and unslopped.
+
+Guidelines:
+1. Implement the requested task directly and surgically in the workspace.
+2. Maintain high engineering rigor: run test suites and verification commands before completing.
+3. Keep diffs minimal and clean. Avoid narrating comments or speculative code.
+4. Conclude with a clear summary and status: you MUST include either [STATUS: PASS] or [STATUS: ISSUES] at the end of your final response.
+"""
+
def emit_milestone(task_index: int, phase: str, detail: str = "") -> None:
cw = sys.modules.get("cloud_worker")
@@ -130,6 +140,16 @@ def emit_tool_result(task_index: int, tool_name: str, result: Any = "", *args: A
"fable-5.1": "fable-5-1",
"publishers/fable/models/fable-5-1": "fable-5-1",
"publishers/fable/models/fable-5.1": "fable-5-1",
+
+ # DeepSeek Model Garden
+ "deepseek": "publishers/deepseek-ai/models/deepseek-v4.1-flash-maas",
+ "deepseek-flash": "publishers/deepseek-ai/models/deepseek-v4.1-flash-maas",
+ "deepseek-4.1": "publishers/deepseek-ai/models/deepseek-v4.1-flash-maas",
+ "deepseek-v4.1": "publishers/deepseek-ai/models/deepseek-v4.1-flash-maas",
+ "deepseek-4.1-flash": "publishers/deepseek-ai/models/deepseek-v4.1-flash-maas",
+ "deepseek-v4.1-flash-maas": "publishers/deepseek-ai/models/deepseek-v4.1-flash-maas",
+ "deepseek-v4.1-flash-maas@001": "publishers/deepseek-ai/models/deepseek-v4.1-flash-maas",
+ "publishers/deepseek-ai/models/deepseek-v4.1-flash-maas": "publishers/deepseek-ai/models/deepseek-v4.1-flash-maas",
}
PARTNER_MODELS = {
@@ -142,13 +162,24 @@ def emit_tool_result(task_index: int, tool_name: str, result: Any = "", *args: A
}
+def is_deepseek_model(model: str) -> bool:
+ if not model:
+ return False
+ return "deepseek" in model.lower().strip()
+
+
def is_partner_model(model: str) -> bool:
if not model:
return False
m = model.lower().strip()
+ if is_deepseek_model(m):
+ return False
if m in PARTNER_MODELS or m in PARTNER_MODEL_ALIASES:
return True
- if m.startswith("publishers/anthropic/") or m.startswith("publishers/fable/"):
+ if (
+ m.startswith("publishers/anthropic/")
+ or m.startswith("publishers/fable/")
+ ):
return True
if (
m.startswith("claude-")
@@ -437,6 +468,24 @@ def _get_aiplatform():
return None
+def _get_genai():
+ if "google.genai" in sys.modules:
+ mod = sys.modules["google.genai"]
+ if mod is not None:
+ return mod
+ return None
+ try:
+ from google import genai
+ return genai
+ except (ImportError, AttributeError):
+ pass
+ try:
+ import google.genai as genai
+ return genai
+ except (ImportError, AttributeError):
+ return None
+
+
def execute_claude_model_agent(
model: str,
task_brief: str,
@@ -473,7 +522,7 @@ def execute_claude_model_agent(
)
system_prompt = (
os.environ.get("AGYSTACK_WORKER_SYSTEM_PROMPT")
- or "You are a specialized poteto coding delegate for agystack."
+ or DEFAULT_PARTNER_SYSTEM_PROMPT
)
messages: List[Dict[str, Any]] = [
{"role": "user", "content": task_brief}
@@ -601,7 +650,7 @@ def execute_fable_model_agent(
system_prompt = (
os.environ.get("AGYSTACK_WORKER_SYSTEM_PROMPT")
- or "You are a specialized poteto coding delegate for agystack."
+ or DEFAULT_PARTNER_SYSTEM_PROMPT
)
messages: List[Dict[str, Any]] = [
{"role": "user", "content": task_brief}
@@ -707,6 +756,202 @@ def execute_fable_model_agent(
return "ISSUES", f"Vertex Model Garden Fable execution exception: {exc}"
+def execute_deepseek_model_agent(
+ model: str,
+ task_brief: str,
+ repo_dir: Path,
+ task_item: Any = None,
+ task_index: int = 0,
+ project: Optional[str] = None,
+ location: Optional[str] = None,
+ messenger: Optional[Any] = None,
+) -> Tuple[str, str]:
+ genai = _get_genai()
+ if genai is None:
+ return "BLOCKED", "Missing dependency: google-genai Python package is not installed."
+
+ resolved_project = (
+ project
+ or os.environ.get("VERTEXAI_PROJECT")
+ or os.environ.get("GCP_PROJECT")
+ or os.environ.get("PROJECT_ID")
+ or None
+ )
+ resolved_location = os.environ.get("VERTEXAI_LOCATION") or "global"
+
+ if not model.startswith("publishers/"):
+ model = PARTNER_MODEL_ALIASES.get(model.lower()) or "publishers/deepseek-ai/models/deepseek-v4.1-flash-maas"
+
+ try:
+ import re
+
+ client_kwargs = {
+ "vertexai": True,
+ "project": resolved_project,
+ "location": resolved_location,
+ }
+ if hasattr(genai.types, "HttpOptions"):
+ client_kwargs["http_options"] = genai.types.HttpOptions(timeout=120000)
+
+ client = genai.Client(**client_kwargs)
+
+ function_declarations = [
+ genai.types.FunctionDeclaration(
+ name=t["name"],
+ description=t["description"],
+ parameters=t["input_schema"],
+ )
+ for t in WORKSPACE_TOOLS
+ ]
+ tool = genai.types.Tool(function_declarations=function_declarations)
+
+ system_prompt = (
+ os.environ.get("AGYSTACK_WORKER_SYSTEM_PROMPT")
+ or DEFAULT_PARTNER_SYSTEM_PROMPT
+ )
+ config = genai.types.GenerateContentConfig(
+ system_instruction=system_prompt,
+ tools=[tool],
+ temperature=0.0,
+ )
+
+ contents: List[Any] = [
+ genai.types.Content(
+ role="user",
+ parts=[genai.types.Part.from_text(text=task_brief)],
+ )
+ ]
+ max_turns = 30
+ last_text = ""
+
+ for turn in range(max_turns):
+ response = None
+ for attempt in range(5):
+ try:
+ response = client.models.generate_content(
+ model=model,
+ contents=contents,
+ config=config,
+ )
+ break
+ except Exception as call_exc:
+ err_str = str(call_exc).lower()
+ if (
+ "503" in err_str
+ or "unavailable" in err_str
+ or "429" in err_str
+ or "resource_exhausted" in err_str
+ or "deadline" in err_str
+ or "connection" in err_str
+ ) and attempt < 4:
+ backoff = (2 ** attempt) * 2
+ emit_milestone(task_index, "RETRY", f"Transient {call_exc}; retrying in {backoff}s (attempt {attempt + 1}/5)...")
+ time.sleep(backoff)
+ else:
+ raise
+
+ candidate_parts = []
+ if hasattr(response, "candidates") and response.candidates:
+ c0 = response.candidates[0]
+ if hasattr(c0, "content") and c0.content:
+ candidate_parts = getattr(c0.content, "parts", []) or []
+
+ # Handle thoughts: check part.thought or ... in text
+ for part in candidate_parts:
+ th = getattr(part, "thought", None)
+ if th:
+ if isinstance(th, str) and th.strip():
+ emit_thought(task_index, th.strip())
+ elif isinstance(th, bool) and getattr(part, "text", None):
+ emit_thought(task_index, str(part.text).strip())
+
+ ptxt = getattr(part, "text", None)
+ if ptxt and isinstance(ptxt, str) and not th:
+ for m in re.finditer(r"(.*?)", ptxt, re.DOTALL):
+ val = m.group(1).strip()
+ if val:
+ emit_thought(task_index, val)
+
+ # Check function calls
+ func_calls = []
+ if hasattr(response, "function_calls") and response.function_calls:
+ func_calls = response.function_calls
+ else:
+ for part in candidate_parts:
+ fc = getattr(part, "function_call", None)
+ if fc:
+ func_calls.append(fc)
+
+ if not func_calls:
+ resp_text = None
+ try:
+ resp_text = getattr(response, "text", None)
+ except Exception:
+ pass
+
+ if resp_text and isinstance(resp_text, str) and not candidate_parts:
+ for m in re.finditer(r"(.*?)", resp_text, re.DOTALL):
+ val = m.group(1).strip()
+ if val:
+ emit_thought(task_index, val)
+
+ if resp_text and isinstance(resp_text, str) and resp_text.strip():
+ last_text = resp_text
+ elif candidate_parts:
+ text_pieces = [
+ getattr(p, "text", "") for p in candidate_parts if getattr(p, "text", None)
+ ]
+ last_text = "\n".join(text_pieces).strip()
+
+ if not last_text and turn < max_turns - 1:
+ contents.append(genai.types.Content(
+ role="model",
+ parts=[genai.types.Part.from_text(text="")]
+ ))
+ contents.append(genai.types.Content(
+ role="user",
+ parts=[genai.types.Part.from_text(text="Please state your final summary and conclusion status: [STATUS: PASS] or [STATUS: ISSUES].")]
+ ))
+ continue
+
+ cleaned_text = re.sub(r".*?", "", last_text, flags=re.DOTALL).strip()
+ return _evaluate_agent_status(cleaned_text or last_text)
+
+ # Filter out empty text parts so DeepSeek does not echo empty text on next turn
+ clean_parts = [
+ p for p in candidate_parts
+ if not (getattr(p, "text", None) == "" and not getattr(p, "function_call", None))
+ ]
+ contents.append(genai.types.Content(role="model", parts=clean_parts))
+
+ # Execute workspace tools and append user response parts
+ response_parts = []
+ for fc in func_calls:
+ tool_name = getattr(fc, "name", "")
+ tool_input = getattr(fc, "args", {})
+ if not isinstance(tool_input, dict):
+ try:
+ tool_input = dict(tool_input)
+ except Exception:
+ tool_input = {}
+ emit_tool_invocation(task_index, tool_name, tool_input)
+ result_str = execute_workspace_tool(tool_name, tool_input, repo_dir)
+ emit_tool_result(task_index, tool_name, result_str)
+ response_parts.append(
+ genai.types.Part.from_function_response(
+ name=tool_name,
+ response={"result": result_str},
+ )
+ )
+
+ contents.append(genai.types.Content(role="user", parts=response_parts))
+
+ cleaned_text = re.sub(r".*?", "", last_text, flags=re.DOTALL).strip()
+ return _evaluate_agent_status(cleaned_text or last_text)
+ except Exception as exc:
+ return "ISSUES", f"DeepSeek model execution exception: {exc}"
+
+
def execute_partner_model_agent(
model: str,
task_brief: str,
@@ -717,6 +962,17 @@ def execute_partner_model_agent(
location: Optional[str] = None,
messenger: Optional[Any] = None,
) -> Tuple[str, str]:
+ if is_deepseek_model(model):
+ return execute_deepseek_model_agent(
+ model=model,
+ task_brief=task_brief,
+ repo_dir=repo_dir,
+ task_item=task_item,
+ task_index=task_index,
+ project=project,
+ location=location,
+ messenger=messenger,
+ )
if is_fable_model(model):
return execute_fable_model_agent(
model=model,
diff --git a/tests/test_cloud_preflight_and_worker.py b/tests/test_cloud_preflight_and_worker.py
index a76247c..971ffaa 100644
--- a/tests/test_cloud_preflight_and_worker.py
+++ b/tests/test_cloud_preflight_and_worker.py
@@ -5,6 +5,8 @@
import sys
import tempfile
import unittest
+import urllib
+import urllib.error
from pathlib import Path
from unittest.mock import AsyncMock, MagicMock, patch
@@ -555,6 +557,8 @@ def fake_run(cmd, *args, **kwargs):
)
self.assertEqual(req.get_header("X-goog-user-project"), "my-gcp-project")
self.assertEqual(req.get_header("Authorization"), "Bearer token-xyz")
+ req_body = json.loads(req.data.decode("utf-8"))
+ self.assertEqual(req_body.get("generationConfig", {}).get("maxOutputTokens"), 1)
@patch("urllib.request.urlopen")
@patch("subprocess.run")
@@ -593,6 +597,224 @@ def fake_run(cmd, *args, **kwargs):
)
self.assertEqual(req.get_header("X-goog-user-project"), "my-gcp-project")
+ @patch("urllib.request.urlopen")
+ @patch("subprocess.run")
+ def test_preflight_vertex_partner_model_url_and_guidance(self, mock_subproc, mock_urlopen):
+ def fake_run(cmd, *args, **kwargs):
+ m = MagicMock()
+ m.returncode = 0
+ if "print-access-token" in cmd:
+ m.stdout = "token-xyz\n"
+ else:
+ m.stdout = "HEAD\n"
+ return m
+ mock_subproc.side_effect = fake_run
+
+ # 1. Success case: verifies rawPredict endpoint and resolution to claude-3-opus@20240229 in us-east5
+ mock_resp = MagicMock()
+ mock_resp.__enter__.return_value = mock_resp
+ mock_urlopen.return_value = mock_resp
+
+ run_preflight(
+ repo_url="https://github.com/test/repo.git",
+ gh_token="gh-token",
+ use_vertex=True,
+ gemini_api_key="",
+ project="my-gcp-project",
+ region="global",
+ model="opus-5.5",
+ dry_run=False,
+ gcs_bucket="test-bucket",
+ )
+
+ mock_urlopen.assert_called_once()
+ req = mock_urlopen.call_args[0][0]
+ self.assertEqual(
+ req.full_url,
+ "https://us-east5-aiplatform.googleapis.com/v1/projects/my-gcp-project/locations/us-east5/publishers/anthropic/models/claude-3-opus@20240229:rawPredict",
+ )
+ self.assertEqual(req.get_header("X-goog-user-project"), "my-gcp-project")
+ self.assertEqual(req.get_header("Authorization"), "Bearer token-xyz")
+ req_body = json.loads(req.data.decode("utf-8"))
+ self.assertEqual(req_body.get("anthropic_version"), "vertex-2023-10-16")
+ self.assertEqual(req_body.get("messages"), [{"role": "user", "content": "ping"}])
+ self.assertEqual(req_body.get("max_tokens"), 1)
+
+ # 2. HTTP 404 error case: raises RuntimeError with Model Garden instructions
+ mock_urlopen.reset_mock()
+ http_404 = urllib.error.HTTPError(
+ url=req.full_url,
+ code=404,
+ msg="Not Found",
+ hdrs={},
+ fp=io.BytesIO(b'{"error": {"code": 404, "message": "Publisher model not found"}}'),
+ )
+ mock_urlopen.side_effect = http_404
+
+ with self.assertRaises(RuntimeError) as ctx:
+ run_preflight(
+ repo_url="https://github.com/test/repo.git",
+ gh_token="gh-token",
+ use_vertex=True,
+ gemini_api_key="",
+ project="my-gcp-project",
+ region="global",
+ model="opus-5.5",
+ dry_run=False,
+ gcs_bucket="test-bucket",
+ )
+ err_msg = str(ctx.exception)
+ self.assertIn("opus-5.5", err_msg)
+ self.assertIn("claude-3-opus@20240229", err_msg)
+ self.assertIn("us-east5", err_msg)
+ self.assertIn("404 Not Found", err_msg)
+ self.assertIn("Model Garden", err_msg)
+ self.assertIn("Enable", err_msg)
+
+ @patch("urllib.request.urlopen")
+ @patch("subprocess.run")
+ def test_preflight_vertex_deepseek_model_url(self, mock_subproc, mock_urlopen):
+ def fake_run(cmd, *args, **kwargs):
+ m = MagicMock()
+ m.returncode = 0
+ if "print-access-token" in cmd:
+ m.stdout = "token-xyz\n"
+ else:
+ m.stdout = "HEAD\n"
+ return m
+ mock_subproc.side_effect = fake_run
+
+ mock_resp = MagicMock()
+ mock_resp.__enter__.return_value = mock_resp
+ mock_urlopen.return_value = mock_resp
+
+ run_preflight(
+ repo_url="https://github.com/test/repo.git",
+ gh_token="gh-token",
+ use_vertex=True,
+ gemini_api_key="",
+ project="my-gcp-project",
+ region="global",
+ model="deepseek-v4.1-flash",
+ dry_run=False,
+ gcs_bucket="test-bucket",
+ )
+
+ mock_urlopen.assert_called_once()
+ req = mock_urlopen.call_args[0][0]
+ self.assertEqual(
+ req.full_url,
+ "https://aiplatform.googleapis.com/v1/projects/my-gcp-project/locations/global/publishers/deepseek-ai/models/deepseek-v4.1-flash-maas:generateContent",
+ )
+ self.assertEqual(req.get_header("X-goog-user-project"), "my-gcp-project")
+ self.assertEqual(req.get_header("Authorization"), "Bearer token-xyz")
+ req_body = json.loads(req.data.decode("utf-8"))
+ self.assertEqual(req_body.get("generationConfig", {}).get("maxOutputTokens"), 1)
+
+ @patch("urllib.request.urlopen")
+ @patch("subprocess.run")
+ def test_preflight_deepseek_auto_vertex_and_canonical_alias(self, mock_subproc, mock_urlopen):
+ def fake_run(cmd, *args, **kwargs):
+ m = MagicMock()
+ m.returncode = 0
+ if "print-access-token" in cmd:
+ m.stdout = "token-xyz\n"
+ else:
+ m.stdout = "HEAD\n"
+ return m
+ mock_subproc.side_effect = fake_run
+
+ mock_resp = MagicMock()
+ mock_resp.__enter__.return_value = mock_resp
+ mock_urlopen.return_value = mock_resp
+
+ run_preflight(
+ repo_url="https://github.com/test/repo.git",
+ gh_token="gh-token",
+ use_vertex=False,
+ gemini_api_key="",
+ project="my-gcp-project",
+ region="global",
+ model="deepseek",
+ dry_run=False,
+ gcs_bucket="test-bucket",
+ )
+
+ mock_urlopen.assert_called_once()
+ req = mock_urlopen.call_args[0][0]
+ self.assertEqual(
+ req.full_url,
+ "https://aiplatform.googleapis.com/v1/projects/my-gcp-project/locations/global/publishers/deepseek-ai/models/deepseek-v4.1-flash-maas:generateContent",
+ )
+ self.assertEqual(req.get_header("Authorization"), "Bearer token-xyz")
+ req_body = json.loads(req.data.decode("utf-8"))
+ self.assertEqual(req_body.get("generationConfig", {}).get("maxOutputTokens"), 1)
+
+ @patch("urllib.request.urlopen")
+ @patch("subprocess.run")
+ def test_preflight_vertex_gemini_generation_config_max_output_tokens(self, mock_subproc, mock_urlopen):
+ def fake_run(cmd, *args, **kwargs):
+ m = MagicMock()
+ m.returncode = 0
+ if "print-access-token" in cmd:
+ m.stdout = "token-xyz\n"
+ else:
+ m.stdout = "HEAD\n"
+ return m
+ mock_subproc.side_effect = fake_run
+
+ mock_resp = MagicMock()
+ mock_resp.__enter__.return_value = mock_resp
+ mock_urlopen.return_value = mock_resp
+
+ run_preflight(
+ repo_url="https://github.com/test/repo.git",
+ gh_token="gh-token",
+ use_vertex=True,
+ gemini_api_key="",
+ project="my-gcp-project",
+ region="us-central1",
+ model="gemini-4-argon",
+ dry_run=False,
+ gcs_bucket="test-bucket",
+ )
+
+ mock_urlopen.assert_called_once()
+ req = mock_urlopen.call_args[0][0]
+ req_body = json.loads(req.data.decode("utf-8"))
+ self.assertEqual(req_body.get("generationConfig", {}).get("maxOutputTokens"), 1)
+
+ def test_tier3_dedicated_models_interception(self):
+ for model in ("glm-5-3-flash", "llama-3.3-70b", "GLM-5-3-FLASH", "Llama-3.3-70b", "glm-5.3", "llama-3.3", "glm-5.3-flash", "llama-3.3-70b-instruct"):
+ with self.subTest(model=model):
+ with self.assertRaises(RuntimeError) as ctx:
+ run_preflight(
+ repo_url="https://github.com/org/repo.git",
+ gh_token="test-token",
+ use_vertex=True,
+ gemini_api_key="",
+ project="test-proj",
+ region="us-central1",
+ model=model,
+ dry_run=False,
+ )
+ self.assertIn("Tier 3 open-weights model requiring a dedicated GPU cluster endpoint", str(ctx.exception))
+ self.assertIn("gemini-4-argon, gemini-3.8-flash, deepseek-v4.1-flash", str(ctx.exception))
+
+ def test_cloud_dispatch_list_models_flag(self):
+ import cloud_dispatch
+ with patch("sys.argv", ["cloud_dispatch.py", "--list-models"]), patch("sys.stdout", new_callable=io.StringIO) as mock_out:
+ cloud_dispatch.main()
+ out = mock_out.getvalue()
+ self.assertIn("Vertex AI Model Garden Tiers for Cloud Swarms:", out)
+ self.assertIn("Tier 1: Native MaaS", out)
+ self.assertIn("Tier 2: Gated Partner MaaS", out)
+ self.assertIn("Tier 3: Dedicated GPU Cluster Endpoints", out)
+ self.assertIn("glm-5-3-flash", out)
+ self.assertIn("llama-3.3-70b", out)
+ self.assertIn("gemini-4-argon", out)
+ self.assertIn("deepseek-v4.1-flash", out)
+
@patch("urllib.request.urlopen")
@patch("subprocess.run")
def test_preflight_free_tier_indicator_rejection(self, mock_subproc, mock_urlopen):
@@ -633,6 +855,14 @@ def test_resolve_swarm_model_tier_mappings(self):
self.assertEqual(resolve_swarm_model(cli_model="auto"), "gemini-3.8-flash")
self.assertEqual(resolve_swarm_model(cli_model="inherit-parent"), "gemini-3.8-flash")
self.assertEqual(resolve_swarm_model(cli_model="inherit_parent"), "gemini-3.8-flash")
+ self.assertEqual(resolve_swarm_model(cli_model="argon"), "gemini-4-argon")
+ self.assertEqual(resolve_swarm_model(cli_model="gemini-argon"), "gemini-4-argon")
+ self.assertEqual(resolve_swarm_model(cli_model="gemini"), "gemini-3.8-flash")
+ self.assertEqual(resolve_swarm_model(cli_model="deepseek"), "deepseek-v4.1-flash")
+ self.assertEqual(resolve_swarm_model(cli_model="deepseek-flash"), "deepseek-v4.1-flash")
+ self.assertEqual(cloud_worker.resolve_worker_model("argon"), "gemini-4-argon")
+ self.assertEqual(cloud_worker.resolve_worker_model("deepseek"), "publishers/deepseek-ai/models/deepseek-v4.1-flash-maas")
+ self.assertEqual(cloud_worker.resolve_worker_model("deepseek-flash"), "publishers/deepseek-ai/models/deepseek-v4.1-flash-maas")
def test_resolve_swarm_model_parent_model_inheritance(self):
# Explicit parent_model argument
diff --git a/tests/test_cloud_worker_vertex_partner.py b/tests/test_cloud_worker_vertex_partner.py
index db03553..a9b641e 100644
--- a/tests/test_cloud_worker_vertex_partner.py
+++ b/tests/test_cloud_worker_vertex_partner.py
@@ -19,9 +19,11 @@
emit_thought,
emit_tool_invocation,
emit_tool_result,
+ execute_deepseek_model_agent,
execute_partner_model_agent,
execute_task,
execute_workspace_tool,
+ is_deepseek_model,
is_partner_model,
measure_partner_client_startup,
resolve_worker_model,
@@ -68,11 +70,31 @@ def test_alias_resolution_fable(self):
self.assertEqual(resolved, "fable-5-1")
self.assertTrue(is_partner_model(resolved))
+ def test_alias_resolution_deepseek(self):
+ aliases = [
+ "deepseek",
+ "deepseek-flash",
+ "deepseek-4.1",
+ "deepseek-v4.1",
+ "deepseek-4.1-flash",
+ "deepseek-v4.1-flash-maas",
+ "deepseek-v4.1-flash-maas@001",
+ "publishers/deepseek-ai/models/deepseek-v4.1-flash-maas",
+ ]
+ canonical = "publishers/deepseek-ai/models/deepseek-v4.1-flash-maas"
+ for alias in aliases:
+ resolved = resolve_worker_model(alias)
+ self.assertEqual(resolved, canonical)
+ self.assertTrue(is_deepseek_model(resolved))
+ self.assertFalse(is_partner_model(resolved))
+
def test_gemini_models_are_not_partner_models(self):
gemini_models = [
"gemini-3.8-flash",
"gemini-3.1-pro",
"gemini-3.1-flash-lite",
+ "gemini-4-argon",
+ "argon",
"flash",
"pro",
]
@@ -182,6 +204,21 @@ def test_missing_aiplatform_fallback_blocked_for_fable(self):
"Missing dependency: google-cloud-aiplatform Python package is not installed.",
)
+ def test_missing_genai_fallback_blocked_for_deepseek(self):
+ with patch.dict(sys.modules, {"google.genai": None}):
+ status, summary = execute_task(
+ task_item={"model": "deepseek"},
+ task_brief="Implement deepseek task",
+ repo_dir=self.repo_dir,
+ model_override="deepseek",
+ api_key="",
+ )
+ self.assertEqual(status, "BLOCKED")
+ self.assertEqual(
+ summary,
+ "Missing dependency: google-genai Python package is not installed.",
+ )
+
def test_milestone_emission_partner_model(self):
mock_anthropic = MagicMock()
mock_client = MagicMock()
@@ -232,6 +269,46 @@ def test_milestone_emission_fable_model(self):
2, "RUNNING_AGENT", "Vertex Model Garden agent (fable-5-1)"
)
+ def test_milestone_emission_deepseek_model(self):
+ mock_genai = MagicMock()
+ mock_client = MagicMock()
+ mock_resp = MagicMock()
+ mock_resp.text = "[STATUS: PASS] DeepSeek done."
+ mock_resp.function_calls = []
+ mock_resp.candidates = [
+ MagicMock(
+ content=MagicMock(
+ parts=[
+ MagicMock(
+ text="[STATUS: PASS] DeepSeek done.",
+ thought=None,
+ function_call=None,
+ )
+ ]
+ )
+ )
+ ]
+ mock_client.models.generate_content.return_value = mock_resp
+ mock_genai.Client.return_value = mock_client
+
+ with patch.dict(sys.modules, {"google.genai": mock_genai}), patch(
+ "cloud_worker.emit_milestone"
+ ) as mock_milestone:
+ status, _ = execute_task(
+ task_item={"model": "deepseek"},
+ task_brief="Execute brief",
+ repo_dir=self.repo_dir,
+ model_override="deepseek",
+ api_key="",
+ task_index=2,
+ )
+ self.assertEqual(status, "PASS")
+ mock_milestone.assert_called_with(
+ 2,
+ "RUNNING_AGENT",
+ "Vertex Model Garden agent (publishers/deepseek-ai/models/deepseek-v4.1-flash-maas)",
+ )
+
def test_autonomous_tool_calling_loop_writes_files_and_passes(self):
mock_anthropic = MagicMock()
mock_client = MagicMock()
@@ -302,6 +379,88 @@ def test_autonomous_tool_calling_loop_writes_files_and_passes(self):
mock_inv.assert_any_call(1, "run_command", {"command": "python3 -c 'import solution; assert solution.solve() == 42'"})
self.assertEqual(mock_res.call_count, 2)
+ def test_autonomous_tool_calling_loop_deepseek(self):
+ mock_genai = MagicMock()
+ mock_client = MagicMock()
+
+ def fake_content(role=None, parts=None):
+ c = MagicMock()
+ c.role = role
+ c.parts = parts
+ return c
+ mock_genai.types.Content.side_effect = fake_content
+
+ # Turn 0: DeepSeek emits a thought and calls write_file
+ fc1 = MagicMock()
+ fc1.name = "write_file"
+ fc1.args = {"path": "deepseek_solution.py", "content": "def deepseek_fn(): return 100\n"}
+
+ part_think = MagicMock()
+ part_think.thought = "Thinking about deepseek solution"
+ part_think.text = "Thinking about deepseek solution"
+ part_think.function_call = None
+
+ part_call1 = MagicMock()
+ part_call1.thought = None
+ part_call1.text = None
+ part_call1.function_call = fc1
+
+ resp0 = MagicMock()
+ resp0.function_calls = [fc1]
+ cand0 = MagicMock()
+ cand0.content = MagicMock(parts=[part_think, part_call1])
+ resp0.candidates = [cand0]
+
+ # Turn 1: DeepSeek finishes with PASS
+ part_final = MagicMock()
+ part_final.thought = None
+ part_final.text = "[STATUS: PASS] DeepSeek solution implemented."
+ part_final.function_call = None
+
+ resp1 = MagicMock()
+ resp1.function_calls = []
+ cand1 = MagicMock()
+ cand1.content = MagicMock(parts=[part_final])
+ resp1.candidates = [cand1]
+ resp1.text = "[STATUS: PASS] DeepSeek solution implemented."
+
+ mock_client.models.generate_content.side_effect = [resp0, resp1]
+ mock_genai.Client.return_value = mock_client
+
+ with patch.dict(sys.modules, {"google.genai": mock_genai}), patch(
+ "cloud_worker.emit_thought"
+ ) as mock_thought, patch("cloud_worker.emit_tool_invocation") as mock_inv, patch(
+ "cloud_worker.emit_tool_result"
+ ) as mock_res:
+ status, summary = execute_task(
+ task_item={"model": "deepseek-v4.1-flash"},
+ task_brief="Implement deepseek solution",
+ repo_dir=self.repo_dir,
+ model_override="deepseek-v4.1-flash",
+ api_key="",
+ task_index=2,
+ )
+ self.assertEqual(status, "PASS")
+ self.assertIn("DeepSeek solution implemented.", summary)
+
+ solution_file = self.repo_dir / "deepseek_solution.py"
+ self.assertTrue(solution_file.is_file())
+ self.assertEqual(
+ solution_file.read_text(encoding="utf-8"), "def deepseek_fn(): return 100\n"
+ )
+
+ mock_thought.assert_called_with(2, "Thinking about deepseek solution")
+ mock_inv.assert_called_once_with(
+ 2,
+ "write_file",
+ {"path": "deepseek_solution.py", "content": "def deepseek_fn(): return 100\n"},
+ )
+ self.assertEqual(mock_res.call_count, 1)
+
+ # Verify that the turn sent to generate_content used role="model" (not "assistant")
+ second_call_contents = mock_client.models.generate_content.call_args_list[1][1]["contents"]
+ self.assertEqual(second_call_contents[1].role, "model")
+
def test_autonomous_tool_calling_loop_edit_and_read_file(self):
readme = self.repo_dir / "README.md"
readme.write_text("Hello World\n", encoding="utf-8")
@@ -462,16 +621,50 @@ def test_missing_project_fails(self):
self.assertFalse(result["ok"])
self.assertIn("No active GCP project", result["error"])
- def test_missing_anthropic_fails(self):
+ def test_missing_anthropic_succeeds_with_note(self):
def fake_import(name):
if name == "anthropic":
raise ImportError("No module named 'anthropic'")
return MagicMock()
- result = check_model_garden(project_id="test-project", import_fn=fake_import)
- self.assertFalse(result["ok"])
+ mock_run_cmd = MagicMock(return_value=MagicMock(returncode=0, stdout="Listed 0 items.", stderr=""))
+ result = check_model_garden(project_id="test-project", run_cmd_fn=mock_run_cmd, import_fn=fake_import)
+ self.assertTrue(result["ok"])
self.assertFalse(result["anthropic_installed"])
- self.assertIn("anthropic[vertex]", result["error"])
+ self.assertEqual(
+ result["partner_models_note"],
+ "Optional package 'anthropic[vertex]' not installed for Claude partner models.",
+ )
+ self.assertTrue(result["model_garden_verified"])
+
+ def test_worker_task_manifest_deepseek_enables_vertex_and_global_location(self):
+ mock_genai = MagicMock()
+ mock_client = MagicMock()
+ mock_resp = MagicMock()
+ mock_resp.text = "[STATUS: PASS] DeepSeek task done"
+ mock_resp.function_calls = []
+ mock_resp.candidates = []
+ mock_client.models.generate_content.return_value = mock_resp
+ mock_genai.Client.return_value = mock_client
+
+ with tempfile.TemporaryDirectory() as tmp_dir, patch.dict(sys.modules, {"google.genai": mock_genai}):
+ repo_dir = Path(tmp_dir)
+ task_item = {"brief": "optimize", "model": "deepseek-v4.1-flash"}
+ status, summary = execute_task(
+ task_item=task_item,
+ task_brief="optimize",
+ repo_dir=repo_dir,
+ model_override="",
+ api_key="",
+ use_vertex=True,
+ location="us-central1",
+ task_index=0,
+ )
+ self.assertEqual(status, "PASS")
+ mock_genai.Client.assert_called_once()
+ call_kwargs = mock_genai.Client.call_args[1]
+ self.assertTrue(call_kwargs.get("vertexai"))
+ self.assertEqual(call_kwargs.get("location"), "global")
def test_successful_probe(self):
mock_mod = MagicMock()
@@ -489,6 +682,8 @@ def test_successful_probe(self):
self.assertTrue(result["model_garden_verified"])
self.assertEqual(result["project_id"], "test-project")
self.assertEqual(result["region"], "us-central1")
+ self.assertIn("gemini-4-argon", result["serverless_models"])
+ self.assertIn("deepseek-v4.1-flash-maas", result["serverless_models"])
self.assertIn("claude-3-5-sonnet-v2@20241022", result["partner_models"])
self.assertIn("claude-3-opus@20240229", result["partner_models"])
self.assertIn("fable-5-1", result["partner_models"])
@@ -650,8 +845,14 @@ def test_partner_client_startup_overhead(self):
self.assertLessEqual(duration_s, 2.0)
-if __name__ == "__main__":
- unittest.main()
+class TestWorkspaceTools(unittest.TestCase):
+ def setUp(self):
+ self.temp_dir = tempfile.TemporaryDirectory()
+ self.repo_dir = Path(self.temp_dir.name)
+ subprocess.run(["git", "init"], cwd=self.repo_dir, check=True, capture_output=True)
+
+ def tearDown(self):
+ self.temp_dir.cleanup()
def test_workspace_tool_run_command_destructive_blocked_by_safety(self):
destructive_commands = [
@@ -669,3 +870,34 @@ def test_workspace_tool_run_command_destructive_blocked_by_safety(self):
def test_workspace_tool_run_command_safe_allowed(self):
res = execute_workspace_tool("run_command", {"command": "echo safe_test_output"}, self.repo_dir)
self.assertIn("safe_test_output", res)
+
+
+class TestCloudWorkerManifestAuth(unittest.TestCase):
+ def test_main_deepseek_manifest_enables_vertex_under_adc(self):
+ env = {
+ "REPO_URL": "https://github.com/example/repo.git",
+ "MODEL_OVERRIDE": "",
+ "TASK_MANIFEST": json.dumps([{"model": "deepseek", "brief": "solve"}]),
+ "GEMINI_API_KEY": "",
+ "USE_VERTEX_AI": "",
+ "CLOUD_RUN_TASK_INDEX": "0",
+ }
+ orig_cwd = os.getcwd()
+ try:
+ with patch.dict(os.environ, env, clear=False), \
+ patch("cloud_worker.clone_and_checkout_task"), \
+ patch("cloud_worker.execute_task", return_value=("PASS", "done")) as mock_exec, \
+ patch("cloud_worker.run_command") as mock_run_cmd, \
+ patch("cloud_worker.parse_score_metrics", return_value=(None, None, None)), \
+ patch("os.chdir"):
+ mock_run_cmd.return_value = MagicMock(returncode=0, stdout="")
+ with patch("sys.exit"):
+ cloud_worker.main()
+ mock_exec.assert_called_once()
+ self.assertTrue(mock_exec.call_args[1].get("use_vertex"))
+ finally:
+ os.chdir(orig_cwd)
+
+
+if __name__ == "__main__":
+ unittest.main()