> For the complete documentation index, see [llms.txt](https://docs.talus.network/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.talus.network/talus-docs-v2.1.0/guides/tool-development/build-onchain-tool-vertex-and-upgrade.md).

# Upgrade an On-Chain Tool Without Losing Its Nexus Identity

{% hint style="info" %}
**Audience:** Nexus adopters and integrators.

**Goal:** Upgrade a compatible on-chain Tool and verify its registered identity, schema, authority, and workflow behavior remain safe.
{% endhint %}

Package installation, Tool-pointer mutation, registry endorsement, and live rerun are separate transactions and proofs. None automatically performs another. Install consumer interfaces through the public [Move Registry package records](https://www.moveregistry.com/package/@talus/nexus-interface) and keep `Move.lock` matched to the target network; use the public `nexus-move-packages` repository only for offline source/provenance inspection or a deployment-matched source closure.

Complete [Prepare for On-Chain Development](/talus-docs-v2.1.0/guides/getting-started/prepare-onchain-development.md) before this upgrade guide. Its resolved public dependencies, selected-network package IDs, and committed `Move.lock` are prerequisites for deciding whether the replacement package is compatible.

### Actual actors and authority

| Actor                      | Enforced permission                                                                           |
| -------------------------- | --------------------------------------------------------------------------------------------- |
| Package upgrader           | Owns the package `UpgradeCap`; authorizes a Sui package upgrade in that lineage.              |
| Tool owner                 | Holds `CloneableOwnerCap<OverTool>` bound to the Tool; authorizes its package-pointer change. |
| ToolRegistry administrator | Holds `ToolRegistryAdminCap` bound to the registry; changes `verified` endorsement.           |
| DAG/Task controller        | The DAG owner changes graph/schema; the address or Agent controller operates Tasks.           |

`verified = false` is not a registry-wide execution pause for registered on-chain Tools. A maintenance window is operational: stop known clients from scheduling the DAG and let known Tasks reach a safe terminal/settled state.

#### 1. Establish the baseline

Complete [Build an on-chain Tool](/talus-docs-v2.1.0/guides/tool-development/build-onchain-tool.md), then record:

* package initial/storage IDs, version, `UpgradeCap` ID/owner/policy;
* Tool ID, FQN, module, schema, witness, package pointer, owner capability, ToolCashier relationship, and endorsement;
* DAG ID/owner and its Tool/schema snapshot;
* runtime state and one successful Task, Occurrence, Execution, result, and settlement.

```bash
nexus tool inspect --tool-fqn "$TOOL_FQN" --json > tool-before.json
nexus dag inspect --dag-id "$DAG_ID" --json > dag-before.json
sui client object "$PACKAGE_ID" --json > package-before.json
sui client object "$UPGRADE_CAP_ID" --json > upgrade-cap.json
sui client object "$RUNTIME_STATE_ID" --json > state-before.json
```

Preserve the successful baseline input and scheduling receipt exactly. The post-pointer run must use the same normalized values, not a manually reconstructed equivalent.

#### 2. Quiesce known work

1. Freeze applications and operators that schedule this FQN/DAG.
2. Inventory known Tasks through owned `TaskPointer` objects and recorded Task IDs.
3. Inspect each Occurrence and its Execution.
4. Settle finished work, expire missed occurrences, abort only expired Executions, cancel future work, and close Tasks only after their Schedule is idle and in-flight count is zero.

Use [`nexus task`](/talus-docs-v2.1.0/reference/cli/task.md) and [`nexus execution`](/talus-docs-v2.1.0/reference/cli/execution.md). A public DAG has no reverse index that proves an unknown actor cannot schedule it, so monitor for new work throughout the window.

#### 3. Decide whether a compatible upgrade is possible

Keep the old package when it remains compatible and no behavior change is required. Upgrade when dependency linkage, package compatibility, or an intentional implementation change requires a new package version.

Register a **new Tool identity** instead when the FQN, module, positional input schema, output variants, or witness contract changes incompatibly. The existing Tool's `MetaSchema` is immutable and the pointer operation does not rewrite it.

**Verify the candidate**

Before changing any pointer, verify the package lineage, `UpgradeCap` ownership and policy, dependency IDs and type origins, public `execute` signature, stored `MetaSchema`, witness requirements, and application-state invariants. Build, test, and dry-run the candidate as described below; stop if any check fails or remains unproven.

#### 4. Build, test, dry-run, and install

```bash
export SUI_BUILD_ENV="testnet"
sui move build --build-env "$SUI_BUILD_ENV"
sui move test --build-env "$SUI_BUILD_ENV"
sui client upgrade . \
  --upgrade-capability "$UPGRADE_CAP_ID" \
  --build-env "$SUI_BUILD_ENV" \
  --dry-run \
  --json > upgrade-dry-run.json
```

Stop on any capability, policy, dependency, ABI, schema, witness, environment, or gas failure. Execute the same upgrade without `--dry-run` only after review, save its effects, and extract the unique new package storage ID from the successful published object change:

```bash
export NEW_PACKAGE_ID="0x..."
sui client object "$NEW_PACKAGE_ID" --json > package-after-install.json
```

At this boundary the new package exists, but the registered Tool still points to the old package.

#### 5. Dry-run and migrate the Tool pointer

The V2 LTS SDK target exposes `migrate_on_chain_tool_package_ptb`; the equivalent public Move call is useful when the CLI has no dedicated subcommand:

```bash
sui client ptb \
  --assign tool_package @"$NEXUS_TOOL_PACKAGE_ID" \
  --assign tool @"$TOOL_ID" \
  --assign owner @"$TOOL_OWNER_CAP_ID" \
  --move-call "tool_package::tool_registry::migrate_on_chain_tool_package" \
    tool owner @"$NEW_PACKAGE_ID" \
  --dry-run \
  --json
```

After the dry-run succeeds, execute the same PTB without `--dry-run`. The call:

* requires the owner capability bound to this Tool;
* rejects unregistered and HTTP Tools;
* rejects the pre-upgrade package address;
* preserves Tool ID, FQN, module, witness, schema, owner, registry, and ToolCashier relationship;
* changes only the package pointer and resets `verified` to `false`;
* emits `ToolUpdatedEvent`, which identifies the Tool/FQN but not the new package address.

Always inspect the Tool after the transaction and require its package pointer to equal `NEW_PACKAGE_ID`. An event alone is not sufficient readback.

The same verification reset applies to every identity-bearing Tool migration: changing an off-chain URL, mutable metadata/description, or the on-chain package pointer sets the registry endorsement to `verified = false`. The registration-time `MetaSchema` remains immutable, so an incompatible schema requires a new Tool identity. Revalidate the final URL/metadata/package behavior and obtain fresh registry-admin endorsement before resuming the intended operational trust posture. This endorsement is governance/audit state; it is separate from the Tool's `RegisteredKey` or `External` result-verifier support, which governs how an off-chain result proves itself after the Tool is admitted, and neither is an Invocation-admission gate.

#### 6. Renew endorsement

After reviewing the candidate and pointer readback, the ToolRegistry administrator may set endorsement with the active Nexus Tool package:

```bash
sui client ptb \
  --assign tool_package @"$NEXUS_TOOL_PACKAGE_ID" \
  --assign tool @"$TOOL_ID" \
  --assign registry @"$TOOL_REGISTRY_ID" \
  --assign admin @"$TOOL_REGISTRY_ADMIN_CAP_ID" \
  --move-call "tool_package::tool_registry::set_verified" \
    tool registry admin true \
  --dry-run \
  --json
```

Execute without `--dry-run` only under the bound administrator capability, then inspect the same Tool again. Endorsement is governance/audit state, not traffic containment; keep client scheduling frozen until the live proof passes.

#### 7. Capture the baseline receipt and replay the same operation

Capture the baseline Task, Occurrence, and payment state before the upgrade. Keep the original scheduling receipt with these readbacks; it is the manifest for the replay, not a reason to infer defaults:

```bash
nexus task inspect --task-id "$BASELINE_TASK_ID" --json > baseline-task-state.json
nexus task occurrence inspect \
  --task-id "$BASELINE_TASK_ID" \
  --occurrence-id "$BASELINE_OCCURRENCE_ID" \
  --json > baseline-occurrence-state.json
nexus task occurrence cost \
  --task-id "$BASELINE_TASK_ID" \
  --occurrence-id "$BASELINE_OCCURRENCE_ID" \
  --json > baseline-occurrence-cost.json
```

Record these fields from the saved receipt and readbacks before constructing the replay: `ORIGINAL_OPERATION_KIND` (`dag` or `agent-skill`), the `DAG_ID` or `AGENT_ID` plus `SKILL_ID` selector, `ENTRY_GROUP`, `FUNDING_MODE` (`address` or `agent`), `REFUND_RECIPIENT` when address-funded, `FAILURE_POLICY` (`continue` or `pause`), the exact input file/context, and the original scheduling/timing arguments. Use the same configured signer, network, wallet/session, and Agent authority; those authorization facts are part of the evidence context and are not interchangeable with a different signer. Stop if any required value is absent rather than silently substituting `--dag-id`, `--now`, or a new funding source.

Build the supported `nexus task schedule` arguments conditionally so the replay keeps the baseline selector and policy:

```bash
ORIGINAL_OPERATION_KIND=          # set to dag | agent-skill from the baseline receipt
DAG_ID=                           # set when ORIGINAL_OPERATION_KIND=dag
AGENT_ID=                         # set when ORIGINAL_OPERATION_KIND=agent-skill
SKILL_ID=                         # set when ORIGINAL_OPERATION_KIND=agent-skill
ENTRY_GROUP=                      # set to the exact baseline entry group
FUNDING_MODE=                     # set to address | agent from the baseline receipt
REFUND_RECIPIENT=                 # set to the exact value, or leave empty when absent
REFUND_RECIPIENT_PRESENT=         # set to yes | no from the baseline receipt
FAILURE_POLICY=                   # set to continue | pause from the baseline receipt
INPUT_CONTEXT=baseline-input.json
SCHEDULE_MODE=                    # set to now | at-ms | after-ms | schedule-file
SCHEDULE_VALUE=                   # timestamp/offset for at-ms/after-ms
SCHEDULE_FILE=                    # exact baseline schedule file for schedule-file

: "${ORIGINAL_OPERATION_KIND:?set ORIGINAL_OPERATION_KIND from the baseline receipt}"
: "${ENTRY_GROUP:?set ENTRY_GROUP from the baseline receipt}"
: "${FUNDING_MODE:?set FUNDING_MODE from the baseline receipt}"
: "${FAILURE_POLICY:?set FAILURE_POLICY from the baseline receipt}"
: "${SCHEDULE_MODE:?set SCHEDULE_MODE from the baseline receipt}"

replay_args=(
  nexus task schedule
  --entry-group "$ENTRY_GROUP"
  --input-json "$(jq -c . "$INPUT_CONTEXT")"
  --prepay-amount-mist "$PREPAY_MIST"
  --occurrence-budget-mist "$OCCURRENCE_BUDGET_MIST"
)

case "$ORIGINAL_OPERATION_KIND" in
  dag) test -n "$DAG_ID"; replay_args+=(--dag-id "$DAG_ID") ;;
  agent-skill)
    test -n "$AGENT_ID" && test -n "$SKILL_ID"
    replay_args+=(--agent-id "$AGENT_ID" --skill-id "$SKILL_ID")
    ;;
  *) printf 'unsupported ORIGINAL_OPERATION_KIND: %s\n' "$ORIGINAL_OPERATION_KIND" >&2; exit 1 ;;
esac

case "$FUNDING_MODE" in
  agent) replay_args+=(--agent-funded) ;;
  address)
    case "$REFUND_RECIPIENT_PRESENT" in
      yes) test -n "$REFUND_RECIPIENT"; replay_args+=(--refund-recipient "$REFUND_RECIPIENT") ;;
      no) test -z "$REFUND_RECIPIENT" ;;
      *) printf 'set REFUND_RECIPIENT_PRESENT to yes or no\\n' >&2; exit 1 ;;
    esac
    ;;
  *) printf 'unsupported FUNDING_MODE: %s\n' "$FUNDING_MODE" >&2; exit 1 ;;
esac

case "$FAILURE_POLICY" in
  continue) ;;
  pause) replay_args+=(--pause-on-failure) ;;
  *) printf 'unsupported FAILURE_POLICY: %s\n' "$FAILURE_POLICY" >&2; exit 1 ;;
esac

case "$SCHEDULE_MODE" in
  now) replay_args+=(--now) ;;
  at-ms) test -n "$SCHEDULE_VALUE"; replay_args+=(--at-ms "$SCHEDULE_VALUE") ;;
  after-ms) test -n "$SCHEDULE_VALUE"; replay_args+=(--after-ms "$SCHEDULE_VALUE") ;;
  schedule-file) test -n "$SCHEDULE_FILE"; replay_args+=(--schedule-file "$SCHEDULE_FILE") ;;
  *) printf 'unsupported SCHEDULE_MODE: %s\n' "$SCHEDULE_MODE" >&2; exit 1 ;;
esac

"${replay_args[@]}" --json > replay-task.json
```

If the baseline used a recurrence or another timing option, preserve the exact supported option from the [CLI Task reference](/talus-docs-v2.1.0/reference/cli/task.md) in the manifest and extend the `SCHEDULE_MODE` case before running the replay. Do not represent a different operation as equivalent merely because it reaches the same DAG.

Follow the returned Occurrence, inspect its Execution, settle it, and compare:

* Tool/DAG/FQN/module/schema/witness/runtime-state identities remain stable;
* the operation selector, entry group, funding mode, refund recipient, failure policy, signer/Agent authorization context, and input context match the baseline;
* Tool package pointer is `NEW_PACKAGE_ID` and reviewed endorsement has the intended value;
* normalized `OnchainToolResult` variant/payload and business-state delta match the baseline contract;
* reserve, consumption, settlement, and refund balance under the Task lifecycle.

Task/Occurrence/Execution IDs, transactions, checkpoints, object versions/digests, gas, and latency will differ. Record them without requiring equality.

#### Forward-only failure table

| Failure after       | Real state                                   | Next action                                                                                                                        |
| ------------------- | -------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| Upgrade dry-run     | Nothing committed                            | Correct the candidate and rerun.                                                                                                   |
| Package install     | New package exists; Tool pointer remains old | Validate the installed target, then migrate the pointer; do not blindly reinstall.                                                 |
| Tool pointer change | New pointer committed; endorsement false     | Inspect, review, endorse if appropriate, then live-test.                                                                           |
| Endorsement         | New pointer and reviewed endorsement         | Keep scheduling frozen until live proof; fix forward if behavior fails.                                                            |
| Live rerun          | Package and pointer remain committed         | Recover the Task where possible and ship a forward-compatible recovery release; cross-transaction package rollback is not assumed. |

#### Test evidence and residual gap

Move and SDK tests can prove package-local behavior and pointer-transaction invariants. They do not replace a real package upgrade plus preserved-DAG rerun against the target deployment. The live Task/Occurrence/Execution proof closes that final gap.

#### References and next

* [`nexus_tool::tool_registry`](/talus-docs-v2.1.0/reference/move/nexus_tool/tool_registry.md)
* [Tool transaction builders](/talus-docs-v2.1.0/reference/sdk/transactions.md)
* [`nexus tool`](/talus-docs-v2.1.0/reference/cli/tool.md)
* [Upgrade a TAP package](/talus-docs-v2.1.0/guides/tap-development/upgrade-tap-package.md)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.talus.network/talus-docs-v2.1.0/guides/tool-development/build-onchain-tool-vertex-and-upgrade.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `build a script that syncs our docs to a CMS` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
