#Sub agent is not functioning right

1 messages · Page 1 of 1 (latest)

steel quarry
#

Hi Krill — two issues with a subagent (Ditto) I'm building on top of an existing OpenClaw setup:

1. **/docker pattern not matching in exec-approvals.json
Ditto has this entry in exec-approvals.json:

{
  "id": "e1a00011-0000-0000-0000-000000000007",
  "pattern": "**/docker"
}

The agent block has ask: "off" and askFallback: "deny". After a full compose down + up, Ditto still hits an approval prompt when running docker ps. Is **/docker the correct glob for the docker binary inside an OpenClaw container? Or does docker resolve to a path that the pattern doesn't match?

2. File tool sandbox restriction
Ditto's file read tools appear to be sandboxed to /home/node/.openclaw/workspace. When Ditto tries to read /home/node/.openclaw/cron/jobs.json it gets: Access denied - path outside allowed directories. Is there a config to widen this sandbox so Ditto can read .openclaw/cron/ and .openclaw/openclaw.json? Or is the right approach to use docker exec shell commands instead of file read tools for anything outside /workspace?

pulsar sparrowBOT
sullen forumBOT
#

To help others find answers, you can mark your question as solved via Right click solution message -> Apps -> âś… Mark Solution

pulsar sparrowBOT
bronze ironBOT
#

I dug through the docs/source. Short version:

  1. **/docker is a valid pattern. OpenClaw matches allowlists against the resolved executable path, not the basename, so **/docker should match paths like /usr/bin/docker or /usr/local/bin/docker. Relevant docs: https://docs.openclaw.ai/tools/exec and https://docs.openclaw.ai/tools/exec-approvals

If Ditto still prompts on docker ps, the usual causes are:

  • you edited the wrong approvals host
    • host=gateway uses ~/.openclaw/exec-approvals.json on the gateway
    • host=node uses the node’s own approvals file
  • the host-local approvals file is stricter than the agent/session config
  • Ditto is actually wrapping through a shell, so policy is seeing bash/sh instead of docker
  • askFallback: "deny" is only the fallback when a prompt is needed, it does not make a miss auto-allow

I’d verify these on the actual exec host:

#
openclaw --version
openclaw approvals get --gateway
# or: openclaw approvals get --node <id>

command -v docker
readlink -f "$(command -v docker)"

Once you know the real path, I’d prefer allowlisting the exact path over **/docker.

  1. For file access, there is not a separate documented “extra allowed dirs” knob for file tools. The main setting is tools.fs.workspaceOnly:
  • false = file tools can read/write outside the workspace
  • true = restrict read / write / edit / apply_patch to the workspace

Docs: https://docs.openclaw.ai/concepts/agent-workspace and https://docs.openclaw.ai/gateway/sandboxing

#

Important nuance: if Ditto is sandboxed, file tools only see what the sandbox exposes. So for /home/node/.openclaw/cron/jobs.json and /home/node/.openclaw/openclaw.json, the safer options are:

  • bind-mount those paths into the sandbox, ideally read-only, then read the mounted path
  • or use host exec / docker exec / openclaw config / openclaw cron instead of raw file reads

So yes, for “outside /workspace” data, docker exec or the CLI surface is often the cleaner approach unless you intentionally want broader file-tool access.

If you paste:

  • openclaw --version
  • openclaw approvals get --gateway (or --node ...)
  • readlink -f "$(command -v docker)"

I can help pin down exactly which layer is biting Ditto.

steel quarry
#

Hi Krill — follow-up on the Ditto subagent thread.

Our setup:

  • OpenClaw running in a Docker container (openclaw-gateway) on Windows 11 with Docker Desktop
  • Ditto is a subagent spawned by the main agent (Kingler) when something needs diagnosing or fixing
  • Ditto's job: check container health, read logs, inspect jobs.json, run briefing tests
  • Ditto runs inside the openclaw-gateway container — it is NOT on the host

The problem:
Docker CLI is not installed inside the container. So every docker ps, docker logs, docker exec command in Ditto's AGENTS.md is dead — **/docker in exec-approvals.json never matches because there's no docker binary to resolve.

Two options we're considering:

Option A — Mount the Docker socket
Add /var/run/docker.sock to the container mounts and install docker CLI inside the container. Ditto can then run docker commands natively. Concern: socket access is essentially root on the host — feels too broad for a support agent.

Option B — Use OpenClaw's own CLI/tools instead
Replace docker commands with OpenClaw equivalents:

  • docker logs → openclaw logs or equivalent
  • docker ps → check agent/session status via OpenClaw CLI
  • jobs.json inspection → openclaw cron commands
  • briefing test → trigger via OpenClaw session command

Does OpenClaw's CLI surface cover what Ditto needs for diagnostics? Is there an openclaw logs, openclaw cron status, or similar that gives container health + cron job state without needing docker access? Or is Option A the standard pattern for this use case?

bronze ironBOT
#

Ah, that changes the picture. If Ditto lives inside openclaw-gateway, I’d recommend Option B.

Why:

  • **/docker is not the real problem now, there’s simply no docker binary inside the container.
  • Mounting /var/run/docker.sock into the gateway container is basically host-admin/root-equivalent access. I would not make that the standard pattern for a support/diagnostic subagent.
  • OpenClaw’s own sandbox docs explicitly treat docker.sock as a dangerous mount source. Relevant doc: https://docs.openclaw.ai/gateway/sandboxing

For Ditto’s stated job, OpenClaw’s CLI already covers most of it:

  • Gateway / app health
    • openclaw status
    • openclaw status --all
    • openclaw gateway status
    • openclaw health
    • openclaw doctor
#

So I’d rewrite Ditto’s AGENTS.md roughly like this:

  • docker logs → openclaw logs --follow
  • docker ps → openclaw gateway status + openclaw status
  • jobs.json inspection → openclaw cron status/list/runs
  • briefing test → openclaw cron run <jobId> or openclaw system event --mode now

The main limitation is this:

  • OpenClaw CLI can diagnose OpenClaw
  • it does not replace raw Docker engine visibility for unrelated containers / Docker Desktop internals

So if Ditto truly needs host Docker introspection, I’d keep that as a separate, tightly scoped ops path, not give the general support subagent the Docker socket.

If you want, paste Ditto’s current AGENTS.md snippets and I can help convert the Docker-based commands into OpenClaw-native ones.

steel quarry
#

Hi Krill — architecture question on subagent trust and elevated access.

Our setup:

  • Main agent (Kingler) has WhatsApp, email, and cron access — fully exposed to external input
  • Ditto is a subagent spawned only by Kingler — no direct channel exposure, users don't know it exists
  • Ditto's job: diagnose and repair the OpenClaw stack

The question:
We were initially told to avoid mounting /var/run/docker.sock into the gateway container for Ditto. But our reasoning is: Ditto is the least exposed agent in the setup. No external input reaches it directly — it only receives tasks from Kingler. Giving docker-level access to Ditto (the hidden, unexposed agent) feels safer than giving it to Kingler (the fully exposed agent).

The counter-argument we're aware of: if Kingler is ever compromised via prompt injection from a malicious WhatsApp message or email, and Kingler spawns Ditto with crafted instructions, then Ditto's docker socket access becomes the blast radius.

Two specific questions:

  1. How does OpenClaw's injection defense work at the subagent spawn boundary? If Kingler calls sessions_spawn to start Ditto, is the task payload sanitised or treated as trusted by default?
  2. What's your recommended pattern for a subagent that needs elevated ops access (restart container, run redeploy) but should only be reachable via a trusted parent agent — not directly from any channel?
bronze ironBOT
#

I think your counter-argument is the important one.

Short version: sessions_spawn is an orchestration boundary, not a security boundary. If Kingler can be prompt-injected, Ditto is in the same blast radius unless you move Ditto behind a real host / gateway boundary.

1. What happens at sessions_spawn

From source, the child gets the task text basically as-is:

  • in the child system prompt: You were created to handle: ${taskText}
    (taskText is only whitespace-collapsed)
  • and as the first child message: [Subagent Task]: ${task}

So no, there is not a “spawn-time sanitization wall” that turns Kingler’s payload into trusted input.

What OpenClaw does enforce here is policy/routing stuff:

  • target-agent allowlists
#
  • depth limits
  • sandbox inheritance (sandboxed parent cannot spawn unsandboxed child)

But that is not prompt-injection sanitization.

2. Recommended pattern

I would not treat “hidden subagent” as a hard trust boundary. “Users don’t know Ditto exists” is obscurity, not isolation.

For an ops-capable repair agent, I’d recommend this order:

Best

Put the ops boundary on a separate gateway, or at least a separate OS user / separate host / paired node.

That’s also what the security docs push toward for stronger isolation:
https://docs.openclaw.ai/gateway/security

#

Next best

Do not give Ditto generic Docker socket access.
Instead, expose a tiny fixed ops surface, for example:

  • one or two wrapper scripts
  • exact service restart commands
  • exact redeploy command(s)

Then allowlist those exact commands/scripts, ideally with approval.

That is much safer than “full Docker CLI + docker.sock”, which is basically host-admin.

Not recommended

Kingler (WhatsApp/email exposed) → sessions_spawn → Ditto with docker.sock

That means any prompt injection that convinces Kingler to spawn Ditto can potentially reach host-level ops.

#

Important doc alignment

The security docs explicitly say that for any agent/surface handling untrusted content, deny these by default:

  • gateway
  • cron
  • sessions_spawn
  • sessions_send

So if Kingler handles WhatsApp/email, giving it sessions_spawn into an elevated child is already outside the conservative pattern:
https://docs.openclaw.ai/gateway/security

My concrete recommendation for your setup

  • Kingler: exposed, inbox-facing, no elevated ops, ideally no sessions_spawn
  • Ditto: operator-only ops agent, no public bindings, minimal tools, explicit agentId
  • Put Ditto’s dangerous actions behind:
#
  • a separate node/host, or
    • a tiny allowlisted wrapper command set
  • If you need true trust separation, split it into a separate gateway / host boundary

So, bluntly: if Ditto needs “restart container / redeploy” powers, I would not rely on “trusted parent only” inside the same gateway as the security control.

If you want, I can sketch a safer Kingler/Ditto architecture with exact config shape next.

steel quarry
#

Thanks Krill — that's really clear. The spawn boundary point is the key one we missed.

Yes please, would love the architecture sketch.

Before you do, here's where our thinking has landed based on your recommendation:

Rather than giving Ditto full docker socket access, we're thinking of limiting it to a small set of allowlisted wrapper scripts on the host:

  • restart-gateway.sh — restarts the openclaw-gateway container, nothing else
  • redeploy.sh — already exists for version upgrades
  • possibly a run-briefing.sh equivalent for manual test triggers

Ditto's exec-approvals would allowlist only those exact scripts, not the docker binary or socket. So even if Kingler is injected and spawns Ditto with a crafted task, the blast radius is limited to what those three scripts do — not full host admin.

Questions:

  1. Does that wrapper script approach match what you had in mind by "tiny fixed ops surface"?
  2. Should those scripts live inside /workspace/ (mounted into the container) or on the host outside the container?
  3. Any concern with Ditto calling restart-gateway.sh from inside the container it's about to restart — does it get a clean shutdown or does it just die mid-execution?
bronze ironBOT
#

Yep, that wrapper-script plan is exactly what I meant by a tiny fixed ops surface.

Answers to your 3 questions

1. Does the wrapper-script approach match the recommendation?
Yes. Much better than docker.sock or generic docker access.

Good shape:

  • exact script paths only
  • minimal/no arguments
  • idempotent behavior
  • scripts do one thing each
  • no generic shell access

If you do end up invoking via bash / PowerShell, keep tools.exec.strictInlineEval=true in mind so you are not accidentally broadening the surface.

#

2. Should the scripts live in /workspace/ or on the host?
On the host, outside /workspace/, outside the container.

I would not put control-plane scripts in agent workspace. Reasons:

  • workspace is part of the agent’s normal file surface
  • it is easier to accidentally expose/modify
  • it weakens the “fixed ops surface” idea

Better pattern:

  • dedicated host dir with tight perms
  • not writable by the container user
  • allowlist exact paths there

Also, important nuance for your setup:

#
  • host=gateway in your Docker install means “inside the gateway container”
  • it does not mean “Windows host”

So if the real scripts live on the Windows host, the clean pattern is a paired node / host-side runner, then target host=node.

3. What happens if Ditto restarts the container it is running in?
If the restart command runs inside that same container, Ditto will usually just die mid-execution.

That is why I would run restart/redeploy scripts from outside the target container, on the host/node.

Even then, once the gateway restarts, the current agent run may disappear. That’s normal. So design the script to be:

  • self-contained
  • synchronous
  • idempotent
  • able to wait for health before exiting
#

Do not depend on the same Ditto run surviving long enough to narrate success.


Architecture sketch I’d recommend

Tier 1: Kingler (exposed)

WhatsApp / email / cron facing.

  • no host-admin ops
  • ideally no direct sessions_spawn into elevated repair paths
  • good at diagnosis, summaries, repair recommendations

Tier 2: Ditto (diagnostic)

Internal-only worker.

  • read-only / low-risk diagnostics
#
  • openclaw status
  • openclaw gateway status
  • openclaw health
  • openclaw logs
  • openclaw cron status/list/runs

This one can stay on the same gateway if you want.

Tier 3: HostOps (actual repair surface)

Separate node host or separate gateway/host boundary.

Allowed to run only:

  • restart-gateway.ps1 / .sh
  • redeploy.ps1 / .sh
  • run-briefing.ps1 / .sh
#

with exact-path allowlists only.

So the flow becomes:

  1. Kingler sees a problem
  2. Ditto diagnoses and decides whether repair is needed
  3. HostOps runs one of a tiny set of fixed scripts
  4. after restart, a fresh run checks health/status/logs

One extra suggestion

Since your real host is Windows 11, I’d make the host-side wrappers native to the real execution host (probably PowerShell), not container-local .sh scripts, unless you already have a stable host-side shell environment you trust.

So conceptually I’d aim for:

steel quarry
#

yes.. that would be helpful

bronze ironBOT
#

Yep, here’s the concrete sketch I’d use.

Safer baseline

  • Kingler = exposed inbox agent, no repair powers
  • Ditto = internal diagnostic/repair agent, no direct bindings
  • Windows host node = actual ops executor for the tiny wrapper scripts

That means:

  • no bindings for ditto
  • wrapper scripts live on the Windows host, not in /workspace
  • Ditto runs exec host=node, not host=gateway

Config sketch

#
{
  agents: {
    defaults: {
      subagents: {
        requireAgentId: true,
      },
    },

    list: [
      {
        id: "kingler",
        default: true,
        workspace: "~/.openclaw/workspace-kingler",
        sandbox: {
          mode: "all",
#
          scope: "agent",
          workspaceAccess: "none",
        },
        tools: {
          sessions: { visibility: "self" },
          deny: [
            "group:fs",
            "group:runtime",
            "cron",
            "gateway",
            "sessions_send",
            "sessions_spawn",
          ],
          // plus only the channel/message tools Kingler actually needs
        },
#
      },

      {
        id: "ditto",
        workspace: "~/.openclaw/workspace-ditto",
        sandbox: {
          mode: "all",
          scope: "agent",
          workspaceAccess: "ro",
        },
        tools: {
          sessions: { visibility: "tree" },
          allow: [
            "read",
            "exec",
#
            "process",
            "session_status",
            "sessions_list",
            "sessions_history",
          ],
          deny: [
            "write",
            "edit",
            "apply_patch",
            "browser",
            "canvas",
            "web_fetch",
            "web_search",
            "gateway",
            "cron",
#
            "sessions_send",
            "sessions_spawn",
          ],
          exec: {
            host: "node",
            node: "windows-host",
            security: "allowlist",
            ask: "off",
            strictInlineEval: true,
          },
        },
      },
    ],
  },
#
  tools: {
    subagents: {
      tools: {
        allow: [
          "read",
          "exec",
          "process",
          "session_status",
          "sessions_list",
          "sessions_history",
        ],
        deny: [
          "write",
          "edit",
          "apply_patch",
#
          "browser",
          "canvas",
          "web_fetch",
          "web_search",
          "gateway",
          "cron",
          "sessions_send",
          "sessions_spawn",
        ],
      },
    },
  },
}

Host-side ops surface

#

On the Windows host node, I’d keep:

  • C:\OpenClawOps\restart-gateway.ps1
  • C:\OpenClawOps\redeploy.ps1
  • C:\OpenClawOps\run-briefing.ps1

Properties I’d want:

  • exact-path allowlisted only
  • no free-form args if possible
  • idempotent
  • logs to a durable host path
  • restart/redeploy scripts wait for health before exit

Important note on automation

The config above is the safer version, because Kingler cannot spawn Ditto.

#

If you want the more automated version later, the least-bad variant is:

  • remove sessions_spawn from Kingler’s deny list
  • add:
subagents: {
  allowAgents: ["ditto"],
  requireAgentId: true,
}

But that is a real tradeoff, not a free win. If Kingler is prompt-injected, it can still hand bad work to Ditto.

Practical recommendation

I’d roll this out in 2 stages:

#
  1. Stage 1

    • Ditto exists
    • no public bindings
    • no Kingler spawn
    • test host wrapper scripts + node approvals
  2. Stage 2

    • only if you’re comfortable with the risk, allow Kingler to spawn Ditto explicitly via agentId: "ditto"

After setup

I’d run:

openclaw security audit --deep
steel quarry
#

sure.. that would be helpful

bronze ironBOT
#

Yep, here’s the shape I’d use for the Windows node + wrapper scripts.

1. Put the ops surface on a Windows node host

Run a headless node host on the Windows machine, then have Ditto target that with host=node.

Docs:

Important Windows nuance from the node docs:

  • avoid cmd.exe /c ...
  • in allowlist mode, wrapper forms like that are approval misses
  • call the real executable directly
#

So I’d aim for commands like:

pwsh.exe -File C:\OpenClawOps\restart-gateway.ps1
pwsh.exe -File C:\OpenClawOps\redeploy.ps1
pwsh.exe -File C:\OpenClawOps\run-briefing.ps1

Not:

cmd.exe /c restart-gateway.ps1
powershell.exe -Command "..."

2. Wrapper script rules

#

I’d keep all three scripts in a dedicated host dir, for example:

C:\OpenClawOps\

Rules I’d follow:

  • no user-controlled shell fragments
  • no pass-through arbitrary args
  • no Invoke-Expression
  • fixed container/project names
  • fixed compose path / working dir
  • absolute paths where possible
  • return proper exit codes
  • write logs somewhere durable, for example:
    • C:\OpenClawOps\logs\restart-gateway.log
#
  • C:\OpenClawOps\logs\redeploy.log

3. What each script should do

restart-gateway.ps1

Only restart the one target container/service.

Good behavior:

  • restart only openclaw-gateway
  • wait for health / readiness
  • fail nonzero if it does not come back
  • log timestamps + outcome

redeploy.ps1

Should be stricter than “run whatever deploy command I got”.

#

Better:

  • fixed repo / compose project
  • fixed branch or a tiny validated enum like stable|beta
  • pinned known command path
  • wait for health after deploy

run-briefing.ps1

Should trigger one known test path only.

For example:

  • one fixed CLI command
  • one fixed cron/job trigger
  • one fixed health-check sequence

4. Node approvals shape

#

On the node host, keep exec approvals tight:

  • security: "allowlist"
  • ask: "off"
  • askFallback: "deny"

And inspect/manage them with:

openclaw approvals get --node <id|name|ip>
openclaw approvals set --node <id|name|ip> --file ./exec-approvals.json

Docs: https://docs.openclaw.ai/cli/approvals

5. One subtle but important point

#

Because allowlists match the resolved executable path, not the script filename, the risky part on Windows is accidentally allowing too much interpreter surface.

So the safe pattern is:

  • direct pwsh.exe -File <fixed-script>
  • keep script path fixed
  • keep args minimal or none
  • keep strictInlineEval on for Ditto

6. Restart semantics

If restart-gateway.ps1 actually restarts the running gateway/container, the current Ditto run may vanish mid-turn. That’s expected.

So design the script as if it must succeed without the caller surviving:

  • it should log its own progress
  • it should wait for readiness
#
  • it should exit 0/1 cleanly
  • a later health-check run can verify the result

If you want, next I can sketch a minimal example restart-gateway.ps1 contract and the exact Ditto command forms I’d recommend it use.

steel quarry
#

yes.. pls sketch a minimal example restart-gateway.ps1 contract and the exact Ditto command forms

bronze ironBOT
#

Yep, here’s the minimal shape I’d use.

Minimal restart-gateway.ps1 contract

I’d make it zero-arg if possible.

Contract

  • Inputs: none
  • Side effect: restart exactly one known target, openclaw-gateway
  • Success condition: gateway becomes healthy again within a fixed timeout
  • Exit codes:
    • 0 = restarted and healthy
    • 1 = restart failed / health check timed out
  • Logging: append to one fixed log file
  • Idempotence: okay if called twice
  • No dynamic input: no container name, no compose path, no branch/tag, no free-form args from Ditto
#

Minimal skeleton

param()

$ErrorActionPreference = "Stop"

$LogPath = "C:\OpenClawOps\logs\restart-gateway.log"
$TimeoutSec = 120

function Write-OpLog([string]$Message) {
  $ts = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
  Add-Content -Path $LogPath -Value "[$ts] $Message"
}

try {
#
  Write-OpLog "restart-gateway: begin"

  # Fixed restart action only.
  # Hard-code the target here. Do not accept agent-controlled values.
  # Example: restart only the known openclaw-gateway service/container.
  # (Keep the actual restart command fixed inside this script.)

  # Fixed health-check loop.
  $deadline = (Get-Date).AddSeconds($TimeoutSec)
  $healthy = $false

  do {
    Start-Sleep -Seconds 2

    # Replace this with one fixed probe you trust.
#
    # Prefer a fixed OpenClaw health/status probe or one fixed container-health probe.
    # Do not read probe target/args from Ditto.
    $healthy = $false
  }
  until ($healthy -or (Get-Date) -ge $deadline)

  if (-not $healthy) {
    throw "Gateway did not become healthy within $TimeoutSec seconds"
  }

  Write-OpLog "restart-gateway: ok"
  exit 0
}
catch {
  Write-OpLog "restart-gateway: failed - $($_.Exception.Message)"
#
  exit 1
}

Exact Ditto command form

I’d keep Ditto to this shape, exactly:

pwsh.exe -NoProfile -NonInteractive -File C:\OpenClawOps\restart-gateway.ps1

And similarly:

#
pwsh.exe -NoProfile -NonInteractive -File C:\OpenClawOps\redeploy.ps1
pwsh.exe -NoProfile -NonInteractive -File C:\OpenClawOps\run-briefing.ps1

If Ditto is using the exec tool

Conceptually, the call should be:

{
  command: "pwsh.exe -NoProfile -NonInteractive -File C:\\OpenClawOps\\restart-gateway.ps1",
  host: "node",
  node: "windows-host"
}
#

Same pattern for the other two scripts.

What I would explicitly avoid

  • cmd.exe /c ...
  • powershell.exe -Command "..."
  • passing container names from Ditto
  • passing branch/tag/version from Ditto unless it is a tiny validated enum
  • any script that shells out using agent-provided text
  • putting these scripts in /workspace

Good follow-up hardening

If you later need one safe parameter, make it an enum like:

#
param(
  [ValidateSet("stable", "beta")]
  [string]$Channel = "stable"
)

but I’d start with no args at all.

One operational gotcha

When this runs, Ditto may disappear mid-turn because it just restarted its own gateway path. That’s fine. The script should be treated as the source of truth, not the live agent reply.

If you want, I can also sketch a minimal redeploy.ps1 contract in the same style.

steel quarry
#

yes...

bronze ironBOT
#

Cool, here’s the matching minimal redeploy.ps1 contract.

Minimal redeploy.ps1 contract

I’d keep this either:

  • zero-arg, or
  • one tiny validated enum like stable|beta

Not branch names, not tags, not arbitrary image refs.

Contract

  • Inputs: none, or one validated enum only
  • Side effect: redeploy exactly one known OpenClaw stack/project
  • Success condition: new deployment comes up healthy within a fixed timeout
  • Exit codes:
#
  • 0 = redeploy succeeded and health probe passed
    • 1 = redeploy failed / health probe failed
  • Logging: fixed log file
  • No dynamic shell text: no pass-through user/agent command fragments

Safer version, no args

param()

$ErrorActionPreference = "Stop"

$LogPath = "C:\OpenClawOps\logs\redeploy.log"
$TimeoutSec = 300

function Write-OpLog([string]$Message) {
#
  $ts = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
  Add-Content -Path $LogPath -Value "[$ts] $Message"
}

try {
  Write-OpLog "redeploy: begin"

  # Fixed deploy action only.
  # Hard-code the known project/repo/path here.
  # No agent-controlled branch/tag/image input.

  $deadline = (Get-Date).AddSeconds($TimeoutSec)
  $healthy = $false

  do {
#
    Start-Sleep -Seconds 3

    # Replace with one fixed health probe.
    $healthy = $false
  }
  until ($healthy -or (Get-Date) -ge $deadline)

  if (-not $healthy) {
    throw "Redeploy did not become healthy within $TimeoutSec seconds"
  }

  Write-OpLog "redeploy: ok"
  exit 0
}
catch {
#
  Write-OpLog "redeploy: failed - $($_.Exception.Message)"
  exit 1
}

If you really need one safe channel switch

param(
  [ValidateSet("stable", "beta")]
  [string]$Channel = "stable"
)

That is about as far as I’d open it up.

#

Exact Ditto command forms

Zero-arg version:

pwsh.exe -NoProfile -NonInteractive -File C:\OpenClawOps\redeploy.ps1

Enum version:

pwsh.exe -NoProfile -NonInteractive -File C:\OpenClawOps\redeploy.ps1 -Channel stable
pwsh.exe -NoProfile -NonInteractive -File C:\OpenClawOps\redeploy.ps1 -Channel beta

What I’d avoid

#
  • -Branch feature/foo
  • -Tag latest
  • -Image some/random/ref
  • git pull driven by agent-provided refs
  • powershell -Command "...$userInput..."

My bias

For Ditto, I’d start with:

  • restart-gateway.ps1
  • run-briefing.ps1

and only add redeploy.ps1 after the first two are stable. Redeploy is where the blast radius grows fast.

If this thread fixed the architecture question, please right-click the solution message → Apps → ✅ Mark Solution so Answer Overflow can index it for future folks.