Metadata-Version: 2.5
Name: langchain-nomac
Version: 0.1.0
Summary: A macOS sandbox for LangChain Deep Agents: a cloud Mac with Xcode from NoMac, billed per minute.
Project-URL: Homepage, https://nomac.app/platform
Project-URL: Documentation, https://nomac.app/docs/sdks
License-Expression: MIT
License-File: LICENSE
Keywords: agents,deepagents,ios,langchain,mac,macos,nomac,sandbox,xcode
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Typing :: Typed
Requires-Python: <4.0,>=3.11
Requires-Dist: deepagents<0.8.0,>=0.7.35
Requires-Dist: nomac<2.0.0,>=1.0.0
Description-Content-Type: text/markdown

# langchain-nomac

A macOS sandbox for [LangChain Deep Agents](https://docs.langchain.com/oss/python/deepagents/sandboxes): a [NoMac](https://nomac.app) Mac, a cloud Mac with Xcode, as your agent's shell and files. The agent builds and tests apps for iPhone, iPad and the Mac, signs them and runs anything else macOS runs. On demand, billed per minute from prepaid credit, no subscription.

```sh
pip install langchain-nomac
```

Python 3.11 and later. It brings `deepagents` and [`nomac`](https://pypi.org/project/nomac/), NoMac's SDK, which starts and stops the Mac.

## A Mac for your agent

```python
from deepagents import create_deep_agent
from langchain_nomac import NoMacSandbox
from nomac import NoMac

mac = NoMac().macs.start()  # NOMAC_API_KEY from the environment
mac.wait_until_ready()      # usually under a minute
backend = NoMacSandbox(mac=mac)

agent = create_deep_agent(
    model="anthropic:claude-sonnet-4-6",
    system_prompt="You are an iOS developer with a Mac. Build with xcodebuild.",
    backend=backend,
)

try:
    agent.invoke({"messages": [{"role": "user", "content": "Clone https://github.com/acme/app and run its tests on an iPhone Simulator"}]})
finally:
    mac.stop()  # the Mac and everything outside ~/drive are deleted
```

`NoMacSandbox(mac=..., timeout=1800)` takes a started Mac: `NoMac().macs.start(...)` then `mac.wait_until_ready()`, or `NoMac().mac(id)` for a running one, also with a Mac's own token (`NoMac(token=...)`). `backend.mac` is the Mac itself, for its screen, a link to its desktop, `extend()` and `stop()`. `backend.id` is its session id. Get an API key at [nomac.app](https://nomac.app); the Mac's options (a label for each of your users, limits, a drive) are in the [nomac README](https://pypi.org/project/nomac/).

Always stop the Mac when the agent is done. It also stops by itself at its time limit (2 hours by default), after 10 idle minutes, at its spend cap or when the credit runs out.

## What the agent gets

The Mac runs macOS with Xcode, the iOS Simulator, Homebrew, Python, Node, Ruby, CocoaPods, Git and the GitHub CLI; [nomac.app/llms.txt](https://nomac.app/llms.txt) lists the versions. The agent's user is `agent`, with `sudo` and no password asked, and every macOS permission prompt is answered for it.

- **Commands** (`execute`) run in a login shell (`bash -lc`) as `agent`. The output is stdout and stderr together, in the order they were written; `exit_code` is the command's. A command runs for 30 minutes at most by default (`timeout` on the sandbox or on each call, an hour at most; `0` asks for the hour). One stopped at its timeout answers exit code 124 and says so at the end of its output. Past 8 MiB, NoMac keeps the start and the end of the output, and `truncated` is set.
- **Files** (`upload_files`, `download_files`, and the agent's `write_file`) go byte for byte, any size up to 2 GiB, folders made on the way. Paths must be absolute (else `invalid_path`); a missing file answers `file_not_found`, a folder `is_directory`, a file the agent cannot read `permission_denied`.
- **ls, read, edit, glob and grep** come from Deep Agents' `BaseSandbox` and run with the Mac's Python. grep runs macOS's own grep, with the flags that make it answer as GNU grep does, and stops after 35 seconds.
- **A Mac that has stopped** (its time limit, idle timeout, spend cap or the credit's end) or a NoMac that cannot be reached: `execute` raises `nomac.NoMacError`, whose `code` and `message` say which, and file calls answer it as each file's error.

`~/drive` is kept between Macs: what the agent leaves there is on its next Mac. Start with `access={"drive": "none"}` for a Mac with nothing kept, or with `label="user-42"` for a Mac with that user's own drive.

## Async

Every call has its async form (`aexecute`, `aupload_files`, `adownload_files`, `aread`, `agrep`, ...), which runs the sync call in a thread.

## Tests

```sh
uv sync --group test
uv run pytest tests/unit_tests                                    # offline
NOMAC_API_KEY=nmk_... uv run pytest tests/integration_tests       # a live Mac, about 6 minutes
```

Both run LangChain's standard sandbox tests (`SandboxIntegrationTests` from `langchain-tests`): offline against a fake NoMac whose Mac is the machine running them, live on a NoMac Mac that starts without a drive and is stopped however the run ends. Seven of the standard tests (six on glob, one on `write`) still assert the contract Deep Agents 0.7 replaced, and fail for every sandbox built on `BaseSandbox`; they are marked as expected failures until [langchain-ai/langchain#40283](https://github.com/langchain-ai/langchain/pull/40283) ships, and `tests/standard.py` checks the current contract in their place.

MIT licensed.
