Metadata-Version: 2.4
Name: mishmar
Version: 0.3.1
Summary: Mishmar is a risk management layer for trading bots and AI agents: every order is checked against your limits before it reaches the exchange, large orders wait for your approval, and no key can trade alone. The Python SDK: run the agent's signer, propose orders, read decisions.
License-Expression: Apache-2.0
Project-URL: Homepage, https://mishmar.io
Project-URL: Source, https://github.com/mishmar-io/mishmar
Keywords: trading,trading-bot,ai-agents,risk-management,pre-trade-risk,hyperliquid,binance,mpc,threshold-signatures,mcp
Classifier: Development Status :: 2 - Pre-Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Office/Business :: Financial
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: cryptography>=41
Provides-Extra: test
Requires-Dist: pytest>=8; extra == "test"
Dynamic: license-file

# mishmar (Python)

<!-- mcp-name: io.github.mishmar-io/mishmar -->

[Mishmar](https://mishmar.io) is a risk management layer for trading bots
and AI agents: every order is checked against your limits before it reaches
the exchange, large orders wait for your approval, and no key can trade
alone.

This is the Python client: build a proposal, sign it with the agent's key,
send it to the engine, read the decision.

The package also puts `mishmar` on the path, so `uvx mishmar mcp` runs the
MCP server and `uvx mishmar signer run` the signer without the CLI
installed: the command runs the binary found through `MISHMAR_BIN`, the
PATH or `~/.mishmar/bin`, and when there is none fetches the pinned release
(v0.3.0) into `~/.mishmar/bin` first, checked against the release's
checksums.

```python
from datetime import timedelta
from mishmar import AgentKey, Client, Proposal

key = AgentKey.parse("msk_…")           # shown once when the engine issued the key
client = Client("http://localhost:8080/v1", key, key_id="key_…")

decision = client.propose(Proposal(
    trading_account_id="ta_…", symbol="BTC", side="buy", size="0.25",
    order_type="market", valid_for=timedelta(minutes=5),
))
print(decision["verdict"])              # allow | require_approval | block

# Pull a resting order this agent placed through Mishmar: a cancel names the
# order by the clientOrderId its decision carries, and has no side or size.
client.propose(Proposal(
    trading_account_id="ta_…", symbol="BTC", kind="cancel",
    target_client_order_id=decision["proposal"]["clientOrderId"],
))
```

Sizes, prices and leverage are decimal **strings** and are signed exactly as
written: `"0.5"` and `"0.50"` are the same quantity but different signatures.
Never pass a float.

A call the engine did nothing with goes again by itself: `503 unavailable`
while the engine restarts and `429 rate_limited` (after its `Retry-After`),
for up to 45 s, then the last answer is raised as `EngineError`. Nothing else
is retried. `Client(..., retries=None)` turns that off; `retries=Retries(budget=90)`
tunes it.

Run the tests, which include the cross-language vectors in `../spec/vectors`:

```
pip install -e ".[test]"
python -m pytest
```

## The signer as a child process

With the `mishmar` binary installed (or fetched with `download_binary`),
`Signer` runs it as a child process and the bot holds no secret: the child
has the agent's key and shares, signs each proposal, and sends it one-shot
where it can. Reads go through the child too.

```python
from mishmar import Signer

with Signer() as s:                                   # ~/.mishmar/agent.json
    d = s.order("ta_…", "BTC", "buy", "0.001", price="79500")
    if d["verdict"] == "require_approval":
        d = s.wait(d["id"])
    s.cancel("ta_…", "BTC", d["proposal"]["clientOrderId"])
```

The child stops when this process ends, however it ends: it runs with
`--exit-on-stdin-close` and a pipe on its stdin. To share one signer among
several processes, or keep it connected across restarts, run it on its own
(`mishmar signer run --local unix:/run/mishmar/signer.sock`) and attach with
`Signer.connect("unix:/run/mishmar/signer.sock")`; `stop()` then leaves it
running. One signer runs per config, so a second `Signer()` on a config in
use fails and names the process that holds it.
