> For the complete documentation index, see [llms.txt](https://help.multisig.ledger.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://help.multisig.ledger.com/guides/cli-guides/building-agent-workflows.md).

# Building agent workflows

### What you'll learn

* Why `lem` is a good fit for agent tool-use.
* How to drive `lem` non-interactively in a sandbox.
* Two end-to-end recipes (Bash and Python) for proposing and signing transactions from an agent loop.
* How to expose `lem` to MCP-aware agents.

### Why `lem` is agent-friendly

| Property                                  | What it means for agents                                                                                                                                                                        |
| ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **JSON-by-default output**                | Every command prints a single JSON document to stdout. Feed it straight into the next tool-call without parsing prose. Pass `--pretty` only when a human will read it.                          |
| **Structured errors on the same channel** | Failures print `{ "error": { "code", "message", "suggestion" } }` to stdout and set a non-zero exit code. Agents can branch on `error.code` instead of regexing English.                        |
| **Stable exit codes**                     | `0` success, `2` validation, `3` no signer paired, `4` env misconfig, and `1` everything else. See the exit codes table in **Command reference**.                                               |
| **Idempotent reads**                      | `safe info`, `safe balances`, `safe nonce`, `tx list`, and `tx show` are safe to retry or replay arbitrarily.                                                                                   |
| **Refusal semantics**                     | `tx sign` refuses to double-sign, re-sign an executed tx, or sign past the threshold. Agents can rely on the CLI to reject obviously wrong actions instead of encoding those checks themselves. |
| **Built-in discovery**                    | `lem --help`, `lem <command> --help`, and `lem <command> <subcommand> --help` produce stable, parseable help. Many agent frameworks can derive a tool schema from this output.                  |
| **Deterministic signers for CI**          | `--salt + --index` derives a stable test signer from a single secret. There is no need to plumb a mnemonic through your agent runtime.                                                          |

### Step-by-step

#### 1. Pair a non-interactive signer

For agents that operate on testnets and CI, derive a deterministic signer from a salt held in your secret manager:

```
lem connect --salt "$AGENT_SALT" --index 0
```

For agents that propose on mainnet, **keep a human in the loop**: the agent prepares the transaction, but a human approves on a Ledger device. See the security note at the bottom of this page.

#### 2. Set up env (only if self-hosting endpoints)

If your agent runtime uses Ledger's hosted Transaction Service and RPCs, skip this step. The distributed binary already knows where to call.

If you point `lem` at your own endpoints, export the env vars from the **Command reference** before running commands, or place them in a file passed via `--env`.

#### 3. Call `lem` from a tool-use loop

Build the calling convention into your agent's tool schema. For each `lem` command, define an input schema (the flags) and let the agent unmarshal stdout as JSON. A minimal contract:

* Run the command.
* On exit code 0, parse stdout as JSON and return to the model.
* On non-zero exit code, parse stdout as `{ error: { code, message, suggestion } }` and return that. Many agents will then retry with a corrected call, for example by running `lem connect` after a `CONFIG_ERROR`.

### Recipe: Bash agent harness

Propose a payment, wait for the second signature, and execute, all from one script. Useful as a reference for shaping the tool-call schemas the agent will use.

```
#!/usr/bin/env bash
set -euo pipefail

SAFE="0xYourSafeAddress"
CHAIN_ID="11155111"
TO="0xRecipientAddress"
VALUE="100000000000000"

# 1. Make sure a signer is paired.
if ! lem config show > /dev/null 2>&1; then
  lem connect --salt "$AGENT_SALT" --index 0
fi

# 2. Sanity-check that the signer owns the Safe.
lem safe info --address "$SAFE" --chain-id "$CHAIN_ID" \
  | jq -e --arg signer "$(lem config show | jq -r '.config.address')" \
    '.owners | map(ascii_downcase) | index($signer | ascii_downcase) != null' > /dev/null

# 3. Propose.
PROPOSAL=$(lem tx propose \
  --address "$SAFE" --chain-id "$CHAIN_ID" \
  --to "$TO" --value "$VALUE")
SAFE_TX_HASH=$(echo "$PROPOSAL" | jq -r '.safeTxHash')

# 4. Wait for the threshold to be met (a co-owner signs out of band).
while true; do
  TX=$(lem tx show --safe-tx-hash "$SAFE_TX_HASH")
  HAVE=$(echo "$TX" | jq '.confirmations | length')
  NEED=$(echo "$TX" | jq '.confirmationsRequired')
  if [ "$HAVE" -ge "$NEED" ]; then break; fi
  sleep 30
done

# 5. Execute.
lem tx execute --chain-id "$CHAIN_ID" "$SAFE_TX_HASH"
```

Every line is a JSON-in / JSON-out call that an LLM agent can substitute for `jq` plumbing.

### Recipe: Python agent tool

A minimal Python wrapper that an LLM tool-use agent (LangChain, OpenAI tools, Anthropic tools, etc.) can register as a single tool with multiple subcommands.

```
import json
import subprocess

class LemError(Exception):
    def __init__(self, code: str, message: str, suggestion: str | None = None):
        self.code = code
        self.message = message
        self.suggestion = suggestion
        super().__init__(f"{code}: {message}")

def call_lem(args: list[str]) -> dict:
    """Invoke `lem` and return parsed stdout, or raise LemError on failure."""
    result = subprocess.run(["lem", *args], capture_output=True, text=True)
    payload = json.loads(result.stdout) if result.stdout.strip() else {}
    if result.returncode != 0:
        err = payload.get("error", {})
        raise LemError(
            code=err.get("code", "UNKNOWN"),
            message=err.get("message", result.stderr.strip() or "lem failed"),
            suggestion=err.get("suggestion"),
        )
    return payload

# Example: propose an ETH transfer.
proposal = call_lem([
    "tx", "propose",
    "--address", "0xYourSafeAddress",
    "--chain-id", "11155111",
    "--to", "0xRecipientAddress",
    "--value", "100000000000000",
])
print(proposal["safeTxHash"])
```

For agent tool-use, expose `call_lem` as a single tool whose argument is the array of CLI args. The model can then form arbitrary `lem` invocations and recover gracefully from errors using the code and suggestion fields.

### Exposing `lem` over MCP

The Model Context Protocol (MCP) lets agents discover and invoke external tools. To expose `lem` to an MCP-aware host:

1. Write a thin MCP server (one tool per `lem` subcommand, or a single "run `lem`" tool that accepts an args array. Both work).
2. Map each tool's input schema to the flags documented in **Command reference**.
3. Forward stdout JSON as the tool's success result. Forward the error object as the failure result.
4. Run the MCP server alongside your agent. The host (Claude Desktop, Cursor, etc.) takes care of routing tool-calls.

The minimal-effort version is the "one tool with an args array" approach in the Python recipe above, wrapped in your MCP framework of choice. The more polished version is one MCP tool per `lem` subcommand, with input schemas derived from the flag tables in **Command reference**.

### Security

* **Never give an agent direct access to a mainnet mnemonic.** `--seed` is a footgun outside of testnets. For mainnet, the agent should *propose* transactions with `lem tx propose` against a Safe whose owners are real Ledger devices. Execution and the on-chain signature stay with humans.
* **Treat** `--salt` as a secret. Anyone with the salt + index can sign as that signer. Store it in your secret manager, not in source.
* **Scope agent capabilities.** Even on testnets, you can run agents under a signer that is only an owner of low-value Safes, so an agent gone rogue cannot drain anything important.
* **Audit the trail.** Every proposal made by the CLI carries `origin: { app: "les-multisig-cli" }` in the Transaction Service, so you can filter agent-originated transactions in the Ledger Multisig UI.

### Next steps

* **Command reference**
* [Guides](https://help.multisig.ledger.com/guides)


---

# 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 current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://help.multisig.ledger.com/guides/cli-guides/building-agent-workflows.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

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.
