# AgentMove

> Moves: a coding agent's session on a laptop (Claude Code, Codex, OpenCode, Hermes or OpenClaw) copies itself onto a NoClick agent and carries on there. The laptop keeps its own session.

Your side makes the move and follows it; the agent in the session does the rest, from the guide its prompt names:

```python
move = noclick.AgentMove.create(name="Docs fixer")
print(move.prompt)        # paste into the session you want moved
...
move.refresh()
print(move.status, move.arrived["files"])   # moving, what it carried so far
print(move.agent_id, move.conversation)     # once done
```

The agent's side runs with the move's own key, which its code is redeemed for once (`AgentMove.redeem`): it can
reach nothing but the move, and ends with it.

```python
key = noclick.AgentMove.redeem("nc_move_...", host="ana-mbp")
moving = noclick.AgentMove.current(client=noclick.Client(api_key=key))
moving.upload_session(tar_bytes, harness="claude_code", cwd="/Users/ana/code/acme")
moving.finish()
```

## noclick.AgentMove

```python
class AgentMove
```

A move from a laptop: `waiting` for its code to be redeemed, `moving` while what the agent carries
arrives (`arrived`), `finishing` while the agent is made (`steps`), then `done` with `agent_id` and
`conversation`; `failed` (finish again), `canceled` or `expired`.

| Attribute | Type | Description |
| --- | --- | --- |
| `id` | `str` |  |
| `status` | `str` | One of `waiting`, `moving`, `finishing`, `done`, `failed`, `canceled`, `expired`. |
| `code` | `str | None` | The one-time code (from `create` only): it works once, for 30 minutes. |
| `prompt` | `str | None` | What a person pastes into the session to move (from `create` only): the guide and the code. |
| `guide_url` | `str` | The guide the moving agent follows. |
| `workspace_id` | `str | None` | The space the agent joins. |
| `draft` | `dict[str, Any]` | What the move was made with: `name`, `harness`, `model`, `model_connection`. |
| `source` | `dict[str, Any] | None` | Where it moves from, as the moving agent says: `harness`, `host`, `cwd`, `cli_version`, `session_id`, `title`. |
| `arrived` | `dict[str, Any]` | What arrived so far: `session`, `repo` or `checkout`, `files`, `consent`, `secrets` (names and where each goes), `setup`, `mcp_servers`, `skills`, `memories`, `note`, `instructions`, `plan`, and once finished `carried`; never a value. |
| `steps` | `list[dict[str, Any]]` | The finish's progress, in order: each `name` (agent, space, files, tools, skills, memories, conversation), `label`, `status` (running, done, failed), `detail` and `error`. |
| `lands_as` | `dict[str, Any] | None` | Where it lands (on the moving side): the agent's `name` and `harness`, and `cwd`, the working directory its session moves to. |
| `question` | `str | None` | What a finish asks before it makes anything (a session that carried no MCP server and no skill: does it have none?). |
| `endpoints` | `list[str] | None` | The move's calls (on the moving side), one line each: method, path and what it does. |
| `agent_id` | `str | None` | The agent it made, once done. |
| `conversation` | `str | None` | The agent's conversation holding the moved session. |
| `error` | `str | None` | Why it failed. |
| `expires_at` | `Any` | When it ends if nobody finishes it. |
| `created_at` | `Any` |  |

### AgentMove.create

```python
noclick.AgentMove.create(
    *, workspace=None, name=None, harness=None, model=None, model_connection=None,
    user=..., client=None
) -> AgentMove
```

Make a move: a one-time code, good once for 30 minutes, and the prompt a person pastes into the
coding-agent session they want moved. The agent there follows the guide the prompt names, carries what
its work needs, and finishes the move, which makes the agent.

Args:
workspace: The space (a `Workspace` or its id) the agent joins.
name: Its name; omitted, the moving agent's choice, else its folder's.
harness: `claude_code`, `codex`, `opencode`, `hermes` or `openclaw`; omitted, the session's own.
model: Its model in the harness's format; omitted, the session's or the harness's default.
model_connection: The connection holding its model's key or plan; omitted, the account's own that runs it.

Parameters:

- `workspace` (`Any`, default `None`)
- `name` (`str | None`, default `None`)
- `harness` (`str | None`, default `None`)
- `model` (`str | None`, default `None`)
- `model_connection` (`Any`, default `None`)
- `user` (`Any`, default: the current user): Who the call acts for. Defaults to the user set by `noclick.as_user` (none: the account itself).
- `client` (`Client | None`, default `None`): The client to send with; defaults to the module client built from `NOCLICK_API_KEY`.

Returns: `AgentMove` The move, with `code` and `prompt` (only here).

Usage:

```python
move = noclick.AgentMove.create(name="Docs fixer", harness="claude_code")
print(move.prompt)
```

### AgentMove.from_id

```python
noclick.AgentMove.from_id(move_id, *, client=None, user=...) -> AgentMove
```

A lazy reference to a move by id.

Parameters:

- `move_id` (`str`, required)
- `client` (`Client | None`, default `None`): The client to send with; defaults to the module client built from `NOCLICK_API_KEY`.
- `user` (`Any`, default: the current user): Who the call acts for. Defaults to the user set by `noclick.as_user` (none: the account itself).

Returns: `AgentMove` A move that loads the first time you read a field. Nothing is sent yet.

Usage:

```python
move = noclick.AgentMove.from_id("8c1f3e2a-4b5d-4e6f-9a7b-0c1d2e3f4a5b")
print(move.status)
```

### AgentMove.cancel

```python
agent_move.cancel() -> AgentMove
```

Call the move off: its key stops working and what arrived goes. A finished move's agent stays.

Returns: `AgentMove` This move, `canceled`.

Usage:

```python
noclick.AgentMove.from_id("8c1f3e2a-4b5d-4e6f-9a7b-0c1d2e3f4a5b").cancel()
```

### AgentMove.connect_sign_in

```python
agent_move.connect_sign_in(name, credential) -> AgentMove
```

Give a tool server the move carried needing sign-in the person's sign-in to it: the `mcp_oauth`
connection NoClick's own MCP sign-in made for its URL.

Parameters:

- `name` (`str`, required)
- `credential` (`Any`, required)

Returns: `AgentMove` This move, its `carried` entry for the server no longer needing sign-in.

Usage:

```python
noclick.AgentMove.from_id("8c1f3e2a-4b5d-4e6f-9a7b-0c1d2e3f4a5b").connect_sign_in("notion", "cr_1")
```

### AgentMove.redeem

```python
noclick.AgentMove.redeem(code, *, host=None, base_url=None) -> str
```

Redeem a move code for the move's own key, once. Keep it out of anything you print or save in your
transcript: write it to a file only you read.

Parameters:

- `code` (`str`, required)
- `host` (`str | None`, default `None`)
- `base_url` (`str | None`, default `None`)

Returns: `str` The key: it reaches only this move, for an hour.

Usage:

```python
key = noclick.AgentMove.redeem("nc_move_...", host="ana-mbp")
```

### AgentMove.current

```python
noclick.AgentMove.current(*, client=None) -> AgentMove
```

The move the client's key belongs to (a move's own key, from `redeem`).

Parameters:

- `client` (`Client | None`, default `None`): The client to send with; defaults to the module client built from `NOCLICK_API_KEY`.

Returns: `AgentMove` The move, with `lands_as` and the limits.

Usage:

```python
moving = noclick.AgentMove.current(client=noclick.Client(api_key=key))
print(moving.lands_as["cwd"])
```

### AgentMove.set_source

```python
agent_move.set_source(
    *, harness, host, cwd, cli_version=None, session_id=None, title=None, model=None
) -> AgentMove
```

Say where the session moves from: its harness, machine and folder.

Parameters:

- `harness` (`str`, required)
- `host` (`str`, required)
- `cwd` (`str`, required)
- `cli_version` (`str | None`, default `None`)
- `session_id` (`str | None`, default `None`)
- `title` (`str | None`, default `None`)
- `model` (`str | None`, default `None`)

Returns: `AgentMove` This move.

Usage:

```python
moving.set_source(harness="claude_code", host="ana-mbp", cwd="/Users/ana/code/acme")
```

### AgentMove.upload_session

```python
agent_move.upload_session(data, *, harness, cwd, session_id=None) -> AgentMove
```

The session's own files: a tar of paths relative to the harness's home (`~/.claude` for Claude Code).

Parameters:

- `data` (`bytes`, required)
- `harness` (`str`, required)
- `cwd` (`str`, required)
- `session_id` (`str | None`, default `None`)

Returns: `AgentMove` This move; `arrived["session"]` says what NoClick read.

Usage:

```python
moving.upload_session(tar_bytes, harness="claude_code", cwd="/Users/ana/code/acme", session_id=sid)
```

### AgentMove.repo

```python
agent_move.repo(*, name=None, branch=None) -> dict[str, Any]
```

A NoClick repo for the code (made once) and a push `url`, good for 10 minutes: treat it as a secret.

Parameters:

- `name` (`str | None`, default `None`)
- `branch` (`str | None`, default `None`)

Returns: `dict[str, Any]` `repo_id`, `name`, `branch`, `path`, `url`, `expires_at` and `cwd`.

Usage:

```python
pushed = moving.repo()
# git push <pushed["url"]> HEAD:refs/heads/<pushed["branch"]>
```

### AgentMove.put_file

```python
agent_move.put_file(path, data) -> AgentMove
```

A file from outside the repo, placed at `/workspace/<path>` (up to 100 MB).

Parameters:

- `path` (`str`, required)
- `data` (`bytes`, required)

Returns: `AgentMove` This move.

Usage:

```python
moving.put_file("notes/spec.md", open("/Users/ana/notes/spec.md", "rb").read())
```

### AgentMove.consent

```python
agent_move.consent(secrets, *, confirmed) -> AgentMove
```

The secrets the person said yes to carrying, each `{name, kind, to}` (what it is and where it goes, as
they read it; never a value). `confirmed=True` once they said yes in the session. A secret is carried only
once it's listed here; this replaces the list sent before.

Parameters:

- `secrets` (`list[Mapping[str, str]]`, required)
- `confirmed` (`bool`, required)

Returns: `AgentMove` This move; `arrived["consent"]` lists them.

Usage:

```python
moving.consent([{"name": "linear-token", "kind": "Linear API key", "to": "env LINEAR_API_KEY"}],
               confirmed=True)
```

### AgentMove.put_secret

```python
agent_move.put_secret(name, value, *, to=None) -> AgentMove
```

One secret the session uses, sealed as it lands, and where it goes in the copy: `{"env": NAME}`,
`{"file": path, "mode": "600"}`, `{"model": provider}` (the model key it runs on), or none for one a tool
server's header or the checkout refers to by name.

Parameters:

- `name` (`str`, required)
- `value` (`str`, required)
- `to` (`Mapping[str, Any] | None`, default `None`)

Returns: `AgentMove` This move; `arrived["secrets"]` names it, never its value.

Usage:

```python
moving.put_secret("linear-token", os.environ["LINEAR_API_KEY"], to={"env": "LINEAR_API_KEY"})
```

### AgentMove.set_setup

```python
agent_move.set_setup(commands) -> AgentMove
```

What the copy's sandbox runs as it starts (after its secrets' files are written): the CLIs and packages
the session used.

Parameters:

- `commands` (`list[str]`, required)

Returns: `AgentMove` This move.

Usage:

```python
moving.set_setup(["npm install -g @linear/cli"])
```

### AgentMove.set_checkout

```python
agent_move.set_checkout(
    url, branch, *, path=None, secret=None, username=None
) -> AgentMove
```

The session's own git remote (an https URL), checked out in the copy at `branch` (where the session
pushed its work), signed in with the carried secret `secret`'s value when it must be.

Parameters:

- `url` (`str`, required)
- `branch` (`str`, required)
- `path` (`str | None`, default `None`)
- `secret` (`str | None`, default `None`)
- `username` (`str | None`, default `None`)

Returns: `AgentMove` This move; `lands_as["cwd"]` is where it's checked out.

Usage:

```python
moving.set_checkout("https://github.com/acme/web.git", "noclick/ana-mbp", secret="github-token")
```

### AgentMove.plan_link

```python
agent_move.plan_link() -> dict[str, Any]
```

The one-tap sign-in to the plan the session's harness runs on (Claude's, ChatGPT's), for the person: its
tokens never move. Once they sign in, the copy runs on it.

Returns: `dict[str, Any]` `{connection, integration, status, url, expires_at}`.

Usage:

```python
print(moving.plan_link()["url"])
```

### AgentMove.add_mcp_server

```python
agent_move.add_mcp_server(
    name, url, *, transport='http', headers=None, sign_in=None
) -> AgentMove
```

A remote MCP server it used (a public https URL); the agent gets its tools. A header's value is text,
or `{"secret": name}` for a carried secret's (a login written as text is refused).

Args:
sign_in: `"browser"` for a server whose login lives in a browser (a claude.ai connector, an OAuth
sign-in): it moves with no headers and no login, and the person signs in to it once on NoClick.

Parameters:

- `name` (`str`, required)
- `url` (`str`, required)
- `transport` (`str`, default `'http'`)
- `headers` (`Mapping[str, Any] | None`, default `None`)
- `sign_in` (`str | None`, default `None`)

Returns: `AgentMove` This move.

Usage:

```python
moving.add_mcp_server("deepwiki", "https://mcp.deepwiki.com/mcp")
moving.add_mcp_server("notion", "https://mcp.notion.com/mcp", sign_in="browser")
```

### AgentMove.add_skill

```python
agent_move.add_skill(name, data) -> AgentMove
```

A skill: its folder as a tar, SKILL.md at its top.

Parameters:

- `name` (`str`, required)
- `data` (`bytes`, required)

Returns: `AgentMove` This move.

Usage:

```python
moving.add_skill("release-notes", tar_bytes)
```

### AgentMove.add_memory

```python
agent_move.add_memory(name, content, *, description) -> AgentMove
```

Something it learned, kept in its NoClick memory.

Parameters:

- `name` (`str`, required)
- `content` (`str`, required)
- `description` (`str`, required)

Returns: `AgentMove` This move.

Usage:

```python
moving.add_memory("deploy-steps", "Deploy with make ship.", description="How this repo deploys")
```

### AgentMove.set_note

```python
agent_move.set_note(text) -> AgentMove
```

The handoff note: what it's in the middle of and what's next. The agent reads it first.

Parameters:

- `text` (`str`, required)

Returns: `AgentMove` This move.

Usage:

```python
moving.set_note("Halfway through the retry fix; next, a test for the third retry.")
```

### AgentMove.set_instructions

```python
agent_move.set_instructions(text) -> AgentMove
```

Its instructions: what it was told for every project (a user-level CLAUDE.md or AGENTS.md).

Parameters:

- `text` (`str`, required)

Returns: `AgentMove` This move.

Usage:

```python
moving.set_instructions(open(os.path.expanduser("~/.claude/CLAUDE.md")).read())
```

### AgentMove.finish

```python
agent_move.finish(
    *, name=None, model=None, model_connection=None, harness=None, none=None
) -> AgentMove
```

Make the agent from what arrived, step by step, and end the move's key. Finish again after a failed step
to pick up there.

Args:
harness: The harness it runs on, when the Draft screen named none (a carried key the session's own
harness doesn't run on is refused naming the ones it does).
model: Its model, in that harness's format, when the Draft screen named none.
none: What the session has none of (`mcp_servers`, `skills`), said outright: a finish that carried
neither asks first (`question`).

Parameters:

- `name` (`str | None`, default `None`)
- `model` (`str | None`, default `None`)
- `model_connection` (`str | None`, default `None`)
- `harness` (`str | None`, default `None`)
- `none` (`Sequence[str] | None`, default `None`)

Returns: `AgentMove` This move: `done`, with `agent_id` and `conversation`; or still `moving` with a `question`.

Usage:

```python
moving.finish()
moving.finish(harness="opencode", model="openrouter/anthropic/claude-sonnet-4.5")
```
