Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions coding-agent/docs/adr/0024-remote-result-publication.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ Suggested production configuration shape:
enabled = true
git_author_name = "coding-agent"
git_author_email = "coding-agent@example.com"
allowed_git_hosts = ["github.com"]
git_token_env = "CODING_AGENT_GIT_TOKEN"

[remote_publication.github]
Expand Down
77 changes: 59 additions & 18 deletions coding-agent/docs/remote-sandbox-production.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,13 @@ allow_request_commands = false
network = "none"
timeout_seconds = 3600
secret_env_allowlist = []

[remote_publication]
enabled = true
git_author_name = "coding-agent"
git_author_email = "coding-agent@example.com"
allowed_git_hosts = ["github.com"]
git_token_env = "CODING_AGENT_GIT_TOKEN"
```

`python:3.11-slim` is enough for basic file operations and pure-Python tools,
Expand Down Expand Up @@ -148,6 +155,7 @@ Run the service on loopback and place TLS/authenticated ingress in front of it:
User=coding-agent
Group=coding-agent
Environment=CODING_AGENT_BEARER_TOKEN=replace-with-secret
Environment=CODING_AGENT_GIT_TOKEN=replace-with-secret
ExecStart=/usr/local/bin/coding-agent serve --config /etc/coding-agent/config.toml
Restart=on-failure
```
Expand Down Expand Up @@ -197,17 +205,15 @@ Use `remote run` for the recommended one-shot remote workflow:
coding-agent remote run team --repo . --goal "fix the failing test"
```

`remote run --repo` uploads a local snapshot, streams one prompt, downloads the
final workspace through the archive manifest/archive API, overwrites the local
checkout while preserving `.git`, and then closes the remote session. Use
`--yes` only when the local checkout is already recoverable and you want to skip
the manifest confirmation prompt; it does not approve tools.
`remote run --repo` uploads a local snapshot, streams one prompt, leaves the
remote session open, and prints follow-up commands for inspecting or exporting
the result. It does not overwrite the local checkout by default.

Set the remote tool approval policy explicitly when automation cannot answer
approval prompts:

```bash
coding-agent remote run team --repo . --goal "fix the failing test" --approval yolo --yes
coding-agent remote run team --repo . --goal "fix the failing test" --approval yolo
```

Select a configured runtime profile when the project needs more tooling than the
Expand All @@ -225,6 +231,37 @@ repeatable bootstrap behavior. Request-provided setup commands remain reserved
for the later setup execution slice and currently fail closed instead of being
accepted as a silent no-op.

For Git-backed sessions, publish remote changes to a review branch explicitly:

```bash
coding-agent remote publish team --session <session-id> --branch coding-agent/session-<session-id>
```

Branch publication requires `[remote_publication]` to be enabled, Git author
identity to be configured, the workspace `remote.origin.url` host to match
`allowed_git_hosts`, and server-side Git credentials. The host allowlist is
checked before token injection or `git push`. When `git_token_env` is set, the
named environment variable must contain a Git token; the server injects it into
the `git push` environment without placing the token in command arguments or
publication responses. If `git_token_env` is omitted, the workspace must rely
on existing Git credential helper state. GitHub PR creation is not part of this
slice.

Snapshot sessions can still be inspected and exported, but they are not branch
publication inputs because snapshots intentionally exclude `.git`.

Use `--download` only when you want the old archive-overwrite fallback:

```bash
coding-agent remote run team --repo . --goal "fix the failing test" --download
```

`--download` fetches the final workspace through the archive manifest/archive
API, overwrites the local checkout while preserving `.git`, and then closes the
remote session. Use `--yes` with `--download` only when the local checkout is
already recoverable and you want to skip the manifest confirmation prompt; it
does not approve tools.

`--approval auto` is the default and can still ask for approval for tools such
as `bash_run`. `--approval interactive` asks for every tool. `--approval yolo`
lets the server approve tool calls for that one remote session, so use it only
Expand All @@ -242,6 +279,9 @@ coding-agent remote sessions status team <session-id>
coding-agent remote sessions cancel team <session-id>
coding-agent remote sessions close team <session-id>

coding-agent remote diff team --session <session-id>
coding-agent remote patch team --session <session-id>
coding-agent remote publish team --session <session-id> --branch coding-agent/session-<session-id>
coding-agent remote download team --session <session-id> --repo .

coding-agent remote workspaces list team-admin
Expand Down Expand Up @@ -279,20 +319,20 @@ providers, or microVM providers.

## Snapshot Transfer Semantics

Remote runs use snapshot round-trip transfer:
Snapshot fallback runs use archive round-trip transfer:

1. The local repo is packed as a bounded `tar.gz` archive encoded as base64.
2. The server extracts that archive into a Docker workspace.
3. The agent executes in the remote workspace.
4. The final remote workspace is downloaded.
5. The local checkout is overwritten while local `.git` is preserved.
4. The final remote workspace remains remote unless explicitly downloaded.
5. Explicit archive download overwrites the local checkout while local `.git`
is preserved.

This is not live sync. P0 does not support incremental patch export, concurrent
local edit merging, efficient large-repo delta sync, or automatic conflict
resolution.
This is not live sync. P0 does not support concurrent local edit merging,
efficient large-repo delta sync, or automatic conflict resolution.

Before using `remote run --repo` or `remote repl --repo`, keep your local work
recoverable with a commit, stash, or backup.
Before using `remote download`, `remote run --download`, or `remote repl
--repo`, keep your local work recoverable with a commit, stash, or backup.

## Production Smoke

Expand All @@ -310,14 +350,15 @@ provider:
```

3. Run a small repository through `remote run --repo`.
4. Confirm the downloaded workspace contains the remote change.
5. Confirm successful one-shot runs leave no active sessions or workspaces:
4. Inspect the remote result with `remote diff` and `remote patch`.
5. Export the result explicitly with `remote download` or close the session.
6. Confirm closed sessions leave no active workspaces:

```bash
coding-agent remote sessions list team
coding-agent remote workspaces list team-admin
```

This verifies production config loading, auth, Docker provider readiness,
workspace execution, approval forwarding, archive manifest/download, local
restore, and session/workspace cleanup.
workspace execution, approval forwarding, result inspection, explicit archive
download, and session/workspace cleanup.
32 changes: 32 additions & 0 deletions coding-agent/src/coding_agent/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1059,6 +1059,38 @@ def remote_patch(name: str, session_id: str) -> None:
click.echo(patch, nl=False)


@remote.command("publish")
@click.argument("name")
@click.option("--session", "session_id", required=True, help="Remote session ID")
@click.option(
"--branch",
"branch_name",
required=True,
help="Publish the remote session changes to this Git branch.",
)
def remote_publish(name: str, session_id: str, branch_name: str) -> None:
"""Publish remote session results as a Git branch."""
from coding_agent.remote.client import get_remote, publish_remote_branch

endpoint = get_remote(name)
publication = publish_remote_branch(endpoint, session_id, branch_name)
published_branch = publication.get("branch_name")
pushed_ref = publication.get("pushed_ref")
commit_sha = publication.get("commit_sha")
remote_url = publication.get("remote_url")
if not isinstance(published_branch, str) or not published_branch:
raise click.ClickException("Remote publish response missing branch_name")
if not isinstance(commit_sha, str) or not commit_sha:
raise click.ClickException("Remote publish response missing commit_sha")
if not isinstance(remote_url, str) or not remote_url:
raise click.ClickException("Remote publish response missing remote_url")
click.echo(f"Published branch {published_branch}")
if isinstance(pushed_ref, str) and pushed_ref:
click.echo(f"Ref: {pushed_ref}")
click.echo(f"Commit: {commit_sha}")
click.echo(f"Remote: {remote_url}")


@remote.command("download")
@click.argument("name")
@click.option("--session", "session_id", required=True, help="Remote session ID")
Expand Down
4 changes: 4 additions & 0 deletions coding-agent/src/coding_agent/environment/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
CloudWorkspaceClientFactory,
CloudWorkspaceSource,
WorkspaceArchiveManifest,
WorkspaceBranchPublication,
WorkspaceDiff,
WorkspaceDiffFile,
WorkspaceInventoryEntry,
Expand All @@ -24,6 +25,7 @@
get_cloud_workspace_from_config,
import_workspace_archive_from_config,
list_cloud_workspaces_from_config,
publish_workspace_branch_from_config,
provision_cloud_binding_from_config,
register_workspace_provider,
workspace_archive_manifest_from_config,
Expand All @@ -43,6 +45,7 @@
"cleanup_stale_cloud_workspaces_from_config",
"LocalEnvironment",
"WorkspaceArchiveManifest",
"WorkspaceBranchPublication",
"WorkspaceDiff",
"WorkspaceDiffFile",
"WorkspaceInventoryEntry",
Expand All @@ -55,6 +58,7 @@
"get_cloud_workspace_from_config",
"import_workspace_archive_from_config",
"list_cloud_workspaces_from_config",
"publish_workspace_branch_from_config",
"provision_cloud_binding_from_config",
"register_workspace_provider",
"workspace_archive_manifest_from_config",
Expand Down
Loading