{"id":13815076,"url":"https://github.com/goodboy/tractor","last_synced_at":"2025-05-15T07:31:52.783Z","repository":{"id":40477409,"uuid":"139894740","full_name":"goodboy/tractor","owner":"goodboy","description":"A distributed, structured concurrency runtime for Python (and friends)","archived":false,"fork":false,"pushed_at":"2025-04-22T08:38:57.000Z","size":2933,"stargazers_count":281,"open_issues_count":128,"forks_count":12,"subscribers_count":7,"default_branch":"main","last_synced_at":"2025-04-22T08:39:56.026Z","etag":null,"topics":["actor-model","async-await","distributed-systems","multicore-programming","multiprocessing","rpc","streaming-data","structured-concurrency","trio"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/goodboy.png","metadata":{"files":{"readme":"docs/README.rst","changelog":"NEWS.rst","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2018-07-05T20:01:14.000Z","updated_at":"2025-04-09T10:37:11.000Z","dependencies_parsed_at":"2024-08-04T04:06:54.988Z","dependency_job_id":"a9c337cb-9cc5-40f9-80ef-f7c1b07e76a8","html_url":"https://github.com/goodboy/tractor","commit_stats":{"total_commits":1290,"total_committers":7,"mean_commits":"184.28571428571428","dds":0.2069767441860465,"last_synced_commit":"649c5e75049bc110c6f06d5dc05552b8d840d423"},"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/goodboy%2Ftractor","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/goodboy%2Ftractor/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/goodboy%2Ftractor/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/goodboy%2Ftractor/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/goodboy","download_url":"https://codeload.github.com/goodboy/tractor/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254295938,"owners_count":22047172,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["actor-model","async-await","distributed-systems","multicore-programming","multiprocessing","rpc","streaming-data","structured-concurrency","trio"],"created_at":"2024-08-04T04:02:55.192Z","updated_at":"2025-05-15T07:31:52.766Z","avatar_url":"https://github.com/goodboy.png","language":"Python","readme":"|logo| ``tractor``: distributed structurred concurrency\n\n|gh_actions|\n|docs|\n\n``tractor`` is a `structured concurrency`_ (SC), multi-processing_ runtime built on trio_.\n\nFundamentally, ``tractor`` provides parallelism via\n``trio``-\"*actors*\": independent Python **processes** (i.e.\n*non-shared-memory threads*) which can schedule ``trio`` tasks whilst\nmaintaining *end-to-end SC* inside a *distributed supervision tree*.\n\nCross-process (and thus cross-host) SC is accomplished through the\ncombined use of our,\n\n- \"actor nurseries_\" which provide for spawning multiple, and\n  possibly nested, Python processes each running a ``trio`` scheduled\n  runtime - a call to ``trio.run()``,\n- an \"SC-transitive supervision protocol\" enforced as an\n  IPC-message-spec encapsulating all RPC-dialogs.\n\nWe believe the system adheres to the `3 axioms`_ of an \"`actor model`_\"\nbut likely **does not** look like what **you** probably *think* an \"actor\nmodel\" looks like, and that's **intentional**.\n\n\nWhere do i start!?\n------------------\nThe first step to grok ``tractor`` is to get an intermediate\nknowledge of ``trio`` and **structured concurrency** B)\n\nSome great places to start are,\n\n- the seminal `blog post`_\n- obviously the `trio docs`_\n- wikipedia's nascent SC_ page\n- the fancy diagrams @ libdill-docs_\n\n\nFeatures\n--------\n- **It's just** a ``trio`` API!\n- *Infinitely nesteable* process trees running embedded ``trio`` tasks.\n- Swappable, OS-specific, process spawning via multiple backends.\n- Modular IPC stack, allowing for custom interchange formats (eg.\n  as offered from `msgspec`_), varied transport protocols (TCP, RUDP,\n  QUIC, wireguard), and OS-env specific higher-perf primitives (UDS,\n  shm-ring-buffers).\n- Optionally distributed_: all IPC and RPC APIs work over multi-host\n  transports the same as local.\n- Builtin high-level streaming API that enables your app to easily\n  leverage the benefits of a \"`cheap or nasty`_\" `(un)protocol`_.\n- A \"native UX\" around a multi-process safe debugger REPL using\n  `pdbp`_ (a fork \u0026 fix of `pdb++`_)\n- \"Infected ``asyncio``\" mode: support for starting an actor's\n  runtime as a `guest`_ on the ``asyncio`` loop allowing us to\n  provide stringent SC-style ``trio.Task``-supervision around any\n  ``asyncio.Task`` spawned via our ``tractor.to_asyncio`` APIs.\n- A **very naive** and still very much work-in-progress inter-actor\n  `discovery`_ sys with plans to support multiple `modern protocol`_\n  approaches.\n- Various ``trio`` extension APIs via ``tractor.trionics`` such as,\n  - task fan-out `broadcasting`_,\n  - multi-task-single-resource-caching and fan-out-to-multi\n    ``__aenter__()`` APIs for ``@acm`` functions,\n  - (WIP) a ``TaskMngr``: one-cancels-one style nursery supervisor.\n\n\nInstall\n-------\n``tractor`` is still in a *alpha-near-beta-stage* for many\nof its subsystems, however we are very close to having a stable\nlowlevel runtime and API.\n\nAs such, it's currently recommended that you clone and install the\nrepo from source::\n\n    pip install git+git://github.com/goodboy/tractor.git\n\n\nWe use the very hip `uv`_ for project mgmt::\n\n    git clone https://github.com/goodboy/tractor.git\n    cd tractor\n    uv sync --dev\n    uv run python examples/rpc_bidir_streaming.py\n\nConsider activating a virtual/project-env before starting to hack on\nthe code base::\n\n    # you could use plain ol' venvs\n    # https://docs.astral.sh/uv/pip/environments/\n    uv venv tractor_py313 --python 3.13\n\n    # but @goodboy prefers the more explicit (and shell agnostic)\n    # https://docs.astral.sh/uv/configuration/environment/#uv_project_environment\n    UV_PROJECT_ENVIRONMENT=\"tractor_py313\n\n    # hint hint, enter @goodboy's fave shell B)\n    uv run --dev xonsh\n\nAlongside all this we ofc offer \"releases\" on PyPi::\n\n    pip install tractor\n\nJust note that YMMV since the main git branch is often much further\nahead then any latest release.\n\n\nExample codez\n-------------\nIn ``tractor``'s (very lacking) documention we prefer to point to\nexample scripts in the repo over duplicating them in docs, but with\nthat in mind here are some definitive snippets to try and hook you\ninto digging deeper.\n\n\nRun a func in a process\n***********************\nUse ``trio``'s style of focussing on *tasks as functions*:\n\n.. code:: python\n\n    \"\"\"\n    Run with a process monitor from a terminal using::\n\n        $TERM -e watch -n 0.1  \"pstree -a $$\" \\\n            \u0026 python examples/parallelism/single_func.py \\\n            \u0026\u0026 kill $!\n\n    \"\"\"\n    import os\n\n    import tractor\n    import trio\n\n\n    async def burn_cpu():\n\n        pid = os.getpid()\n\n        # burn a core @ ~ 50kHz\n        for _ in range(50000):\n            await trio.sleep(1/50000/50)\n\n        return os.getpid()\n\n\n    async def main():\n\n        async with tractor.open_nursery() as n:\n\n            portal = await n.run_in_actor(burn_cpu)\n\n            #  burn rubber in the parent too\n            await burn_cpu()\n\n            # wait on result from target function\n            pid = await portal.result()\n\n        # end of nursery block\n        print(f\"Collected subproc {pid}\")\n\n\n    if __name__ == '__main__':\n        trio.run(main)\n\n\nThis runs ``burn_cpu()`` in a new process and reaps it on completion\nof the nursery block.\n\nIf you only need to run a sync function and retreive a single result, you\nmight want to check out `trio-parallel`_.\n\n\nZombie safe: self-destruct a process tree\n*****************************************\n``tractor`` tries to protect you from zombies, no matter what.\n\n.. code:: python\n\n    \"\"\"\n    Run with a process monitor from a terminal using::\n\n        $TERM -e watch -n 0.1  \"pstree -a $$\" \\\n            \u0026 python examples/parallelism/we_are_processes.py \\\n            \u0026\u0026 kill $!\n\n    \"\"\"\n    from multiprocessing import cpu_count\n    import os\n\n    import tractor\n    import trio\n\n\n    async def target():\n        print(\n            f\"Yo, i'm '{tractor.current_actor().name}' \"\n            f\"running in pid {os.getpid()}\"\n        )\n\n        await trio.sleep_forever()\n\n\n    async def main():\n\n        async with tractor.open_nursery() as n:\n\n            for i in range(cpu_count()):\n                await n.run_in_actor(target, name=f'worker_{i}')\n\n            print('This process tree will self-destruct in 1 sec...')\n            await trio.sleep(1)\n\n            # raise an error in root actor/process and trigger\n            # reaping of all minions\n            raise Exception('Self Destructed')\n\n\n    if __name__ == '__main__':\n        try:\n            trio.run(main)\n        except Exception:\n            print('Zombies Contained')\n\n\nIf you can create zombie child processes (without using a system signal)\nit **is a bug**.\n\n\n\"Native\" multi-process debugging\n********************************\nUsing the magic of `pdbp`_ and our internal IPC, we've\nbeen able to create a native feeling debugging experience for\nany (sub-)process in your ``tractor`` tree.\n\n.. code:: python\n\n    from os import getpid\n\n    import tractor\n    import trio\n\n\n    async def breakpoint_forever():\n        \"Indefinitely re-enter debugger in child actor.\"\n        while True:\n            yield 'yo'\n            await tractor.breakpoint()\n\n\n    async def name_error():\n        \"Raise a ``NameError``\"\n        getattr(doggypants)\n\n\n    async def main():\n        \"\"\"Test breakpoint in a streaming actor.\n        \"\"\"\n        async with tractor.open_nursery(\n            debug_mode=True,\n            loglevel='error',\n        ) as n:\n\n            p0 = await n.start_actor('bp_forever', enable_modules=[__name__])\n            p1 = await n.start_actor('name_error', enable_modules=[__name__])\n\n            # retreive results\n            stream = await p0.run(breakpoint_forever)\n            await p1.run(name_error)\n\n\n    if __name__ == '__main__':\n        trio.run(main)\n\n\nYou can run this with::\n\n    \u003e\u003e\u003e python examples/debugging/multi_daemon_subactors.py\n\nAnd, yes, there's a built-in crash handling mode B)\n\nWe're hoping to add a respawn-from-repl system soon!\n\n\nSC compatible bi-directional streaming\n**************************************\nYes, you saw it here first; we provide 2-way streams\nwith reliable, transitive setup/teardown semantics.\n\nOur nascent api is remniscent of ``trio.Nursery.start()``\nstyle invocation:\n\n.. code:: python\n\n    import trio\n    import tractor\n\n\n    @tractor.context\n    async def simple_rpc(\n\n        ctx: tractor.Context,\n        data: int,\n\n    ) -\u003e None:\n        '''Test a small ping-pong 2-way streaming server.\n\n        '''\n        # signal to parent that we're up much like\n        # ``trio_typing.TaskStatus.started()``\n        await ctx.started(data + 1)\n\n        async with ctx.open_stream() as stream:\n\n            count = 0\n            async for msg in stream:\n\n                assert msg == 'ping'\n                await stream.send('pong')\n                count += 1\n\n            else:\n                assert count == 10\n\n\n    async def main() -\u003e None:\n\n        async with tractor.open_nursery() as n:\n\n            portal = await n.start_actor(\n                'rpc_server',\n                enable_modules=[__name__],\n            )\n\n            # XXX: this syntax requires py3.9\n            async with (\n\n                portal.open_context(\n                    simple_rpc,\n                    data=10,\n                ) as (ctx, sent),\n\n                ctx.open_stream() as stream,\n            ):\n\n                assert sent == 11\n\n                count = 0\n                # receive msgs using async for style\n                await stream.send('ping')\n\n                async for msg in stream:\n                    assert msg == 'pong'\n                    await stream.send('ping')\n                    count += 1\n\n                    if count \u003e= 9:\n                        break\n\n\n            # explicitly teardown the daemon-actor\n            await portal.cancel_actor()\n\n\n    if __name__ == '__main__':\n        trio.run(main)\n\n\nSee original proposal and discussion in `#53`_ as well\nas follow up improvements in `#223`_ that we'd love to\nhear your thoughts on!\n\n.. _#53: https://github.com/goodboy/tractor/issues/53\n.. _#223: https://github.com/goodboy/tractor/issues/223\n\n\nWorker poolz are easy peasy\n***************************\nThe initial ask from most new users is *\"how do I make a worker\npool thing?\"*.\n\n``tractor`` is built to handle any SC (structured concurrent) process\ntree you can imagine; a \"worker pool\" pattern is a trivial special\ncase.\n\nWe have a `full worker pool re-implementation`_ of the std-lib's\n``concurrent.futures.ProcessPoolExecutor`` example for reference.\n\nYou can run it like so (from this dir) to see the process tree in\nreal time::\n\n    $TERM -e watch -n 0.1  \"pstree -a $$\" \\\n        \u0026 python examples/parallelism/concurrent_actors_primes.py \\\n        \u0026\u0026 kill $!\n\nThis uses no extra threads, fancy semaphores or futures; all we need\nis ``tractor``'s IPC!\n\n\"Infected ``asyncio``\" mode\n***************************\nHave a bunch of ``asyncio`` code you want to force to be SC at the process level?\n\nCheck out our experimental system for `guest`_-mode controlled\n``asyncio`` actors:\n\n.. code:: python\n\n    import asyncio\n    from statistics import mean\n    import time\n\n    import trio\n    import tractor\n\n\n    async def aio_echo_server(\n        to_trio: trio.MemorySendChannel,\n        from_trio: asyncio.Queue,\n    ) -\u003e None:\n\n        # a first message must be sent **from** this ``asyncio``\n        # task or the ``trio`` side will never unblock from\n        # ``tractor.to_asyncio.open_channel_from():``\n        to_trio.send_nowait('start')\n\n        # XXX: this uses an ``from_trio: asyncio.Queue`` currently but we\n        # should probably offer something better.\n        while True:\n            # echo the msg back\n            to_trio.send_nowait(await from_trio.get())\n            await asyncio.sleep(0)\n\n\n    @tractor.context\n    async def trio_to_aio_echo_server(\n        ctx: tractor.Context,\n    ):\n        # this will block until the ``asyncio`` task sends a \"first\"\n        # message.\n        async with tractor.to_asyncio.open_channel_from(\n            aio_echo_server,\n        ) as (first, chan):\n\n            assert first == 'start'\n            await ctx.started(first)\n\n            async with ctx.open_stream() as stream:\n\n                async for msg in stream:\n                    await chan.send(msg)\n\n                    out = await chan.receive()\n                    # echo back to parent actor-task\n                    await stream.send(out)\n\n\n    async def main():\n\n        async with tractor.open_nursery() as n:\n            p = await n.start_actor(\n                'aio_server',\n                enable_modules=[__name__],\n                infect_asyncio=True,\n            )\n            async with p.open_context(\n                trio_to_aio_echo_server,\n            ) as (ctx, first):\n\n                assert first == 'start'\n\n                count = 0\n                async with ctx.open_stream() as stream:\n\n                    delays = []\n                    send = time.time()\n\n                    await stream.send(count)\n                    async for msg in stream:\n                        recv = time.time()\n                        delays.append(recv - send)\n                        assert msg == count\n                        count += 1\n                        send = time.time()\n                        await stream.send(count)\n\n                        if count \u003e= 1e3:\n                            break\n\n            print(f'mean round trip rate (Hz): {1/mean(delays)}')\n            await p.cancel_actor()\n\n\n    if __name__ == '__main__':\n        trio.run(main)\n\n\nYes, we spawn a python process, run ``asyncio``, start ``trio`` on the\n``asyncio`` loop, then send commands to the ``trio`` scheduled tasks to\ntell ``asyncio`` tasks what to do XD\n\nWe need help refining the `asyncio`-side channel API to be more\n`trio`-like. Feel free to sling your opinion in `#273`_!\n\n\n.. _#273: https://github.com/goodboy/tractor/issues/273\n\n\nHigher level \"cluster\" APIs\n***************************\nTo be extra terse the ``tractor`` devs have started hacking some \"higher\nlevel\" APIs for managing actor trees/clusters. These interfaces should\ngenerally be condsidered provisional for now but we encourage you to try\nthem and provide feedback. Here's a new API that let's you quickly\nspawn a flat cluster:\n\n.. code:: python\n\n    import trio\n    import tractor\n\n\n    async def sleepy_jane():\n        uid = tractor.current_actor().uid\n        print(f'Yo i am actor {uid}')\n        await trio.sleep_forever()\n\n\n    async def main():\n        '''\n        Spawn a flat actor cluster, with one process per\n        detected core.\n\n        '''\n        portal_map: dict[str, tractor.Portal]\n        results: dict[str, str]\n\n        # look at this hip new syntax!\n        async with (\n\n            tractor.open_actor_cluster(\n                modules=[__name__]\n            ) as portal_map,\n\n            trio.open_nursery() as n,\n        ):\n\n            for (name, portal) in portal_map.items():\n                n.start_soon(portal.run, sleepy_jane)\n\n            await trio.sleep(0.5)\n\n            # kill the cluster with a cancel\n            raise KeyboardInterrupt\n\n\n    if __name__ == '__main__':\n        try:\n            trio.run(main)\n        except KeyboardInterrupt:\n            pass\n\n\n.. _full worker pool re-implementation: https://github.com/goodboy/tractor/blob/master/examples/parallelism/concurrent_actors_primes.py\n\n\nUnder the hood\n--------------\n``tractor`` is an attempt to pair trionic_ `structured concurrency`_ with\ndistributed Python. You can think of it as a ``trio``\n*-across-processes* or simply as an opinionated replacement for the\nstdlib's ``multiprocessing`` but built on async programming primitives\nfrom the ground up.\n\nDon't be scared off by this description. ``tractor`` **is just** ``trio``\nbut with nurseries for process management and cancel-able streaming IPC.\nIf you understand how to work with ``trio``, ``tractor`` will give you\nthe parallelism you may have been needing.\n\n\nWait, huh?! I thought \"actors\" have messages, and mailboxes and stuff?!\n***********************************************************************\nLet's stop and ask how many canon actor model papers have you actually read ;)\n\nFrom our experience many \"actor systems\" aren't really \"actor models\"\nsince they **don't adhere** to the `3 axioms`_ and pay even less\nattention to the problem of *unbounded non-determinism* (which was the\nwhole point for creation of the model in the first place).\n\nFrom the author's mouth, **the only thing required** is `adherance to`_\nthe `3 axioms`_, *and that's it*.\n\n``tractor`` adheres to said base requirements of an \"actor model\"::\n\n    In response to a message, an actor may:\n\n    - send a finite number of new messages\n    - create a finite number of new actors\n    - designate a new behavior to process subsequent messages\n\n\n**and** requires *no further api changes* to accomplish this.\n\nIf you want do debate this further please feel free to chime in on our\nchat or discuss on one of the following issues *after you've read\neverything in them*:\n\n- https://github.com/goodboy/tractor/issues/210\n- https://github.com/goodboy/tractor/issues/18\n\n\nLet's clarify our parlance\n**************************\nWhether or not ``tractor`` has \"actors\" underneath should be mostly\nirrelevant to users other then for referring to the interactions of our\nprimary runtime primitives: each Python process + ``trio.run()``\n+ surrounding IPC machinery. These are our high level, base\n*runtime-units-of-abstraction* which both *are* (as much as they can\nbe in Python) and will be referred to as our *\"actors\"*.\n\nThe main goal of ``tractor`` is is to allow for highly distributed\nsoftware that, through the adherence to *structured concurrency*,\nresults in systems which fail in predictable, recoverable and maybe even\nunderstandable ways; being an \"actor model\" is just one way to describe\nproperties of the system.\n\n\nWhat's on the TODO:\n-------------------\nHelp us push toward the future of distributed `Python`.\n\n- Erlang-style supervisors via composed context managers (see `#22\n  \u003chttps://github.com/goodboy/tractor/issues/22\u003e`_)\n- Typed messaging protocols (ex. via ``msgspec.Struct``, see `#36\n  \u003chttps://github.com/goodboy/tractor/issues/36\u003e`_)\n- Typed capability-based (dialog) protocols ( see `#196\n  \u003chttps://github.com/goodboy/tractor/issues/196\u003e`_ with draft work\n  started in `#311 \u003chttps://github.com/goodboy/tractor/pull/311\u003e`_)\n- We **recently disabled CI-testing on windows** and need help getting\n  it running again! (see `#327\n  \u003chttps://github.com/goodboy/tractor/pull/327\u003e`_). **We do have windows\n  support** (and have for quite a while) but since no active hacker\n  exists in the user-base to help test on that OS, for now we're not\n  actively maintaining testing due to the added hassle and general\n  latency..\n\n\nFeel like saying hi?\n--------------------\nThis project is very much coupled to the ongoing development of\n``trio`` (i.e. ``tractor`` gets most of its ideas from that brilliant\ncommunity). If you want to help, have suggestions or just want to\nsay hi, please feel free to reach us in our `matrix channel`_.  If\nmatrix seems too hip, we're also mostly all in the the `trio gitter\nchannel`_!\n\n.. _structured concurrent: https://trio.discourse.group/t/concise-definition-of-structured-concurrency/228\n.. _distributed: https://en.wikipedia.org/wiki/Distributed_computing\n.. _multi-processing: https://en.wikipedia.org/wiki/Multiprocessing\n.. _trio: https://github.com/python-trio/trio\n.. _nurseries: https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/#nurseries-a-structured-replacement-for-go-statements\n.. _actor model: https://en.wikipedia.org/wiki/Actor_model\n.. _trionic: https://trio.readthedocs.io/en/latest/design.html#high-level-design-principles\n.. _async sandwich: https://trio.readthedocs.io/en/latest/tutorial.html#async-sandwich\n.. _3 axioms: https://www.youtube.com/watch?v=7erJ1DV_Tlo\u0026t=162s\n.. .. _3 axioms: https://en.wikipedia.org/wiki/Actor_model#Fundamental_concepts\n.. _adherance to: https://www.youtube.com/watch?v=7erJ1DV_Tlo\u0026t=1821s\n.. _trio gitter channel: https://gitter.im/python-trio/general\n.. _matrix channel: https://matrix.to/#/!tractor:matrix.org\n.. _broadcasting: https://github.com/goodboy/tractor/pull/229\n.. _modern procotol: https://en.wikipedia.org/wiki/Rendezvous_protocol\n.. _pdbp: https://github.com/mdmintz/pdbp\n.. _pdb++: https://github.com/pdbpp/pdbpp\n.. _cheap or nasty: https://zguide.zeromq.org/docs/chapter7/#The-Cheap-or-Nasty-Pattern\n.. _(un)protocol: https://zguide.zeromq.org/docs/chapter7/#Unprotocols\n.. _discovery: https://zguide.zeromq.org/docs/chapter8/#Discovery\n.. _modern protocol: https://en.wikipedia.org/wiki/Rendezvous_protocol\n.. _messages: https://en.wikipedia.org/wiki/Message_passing\n.. _trio docs: https://trio.readthedocs.io/en/latest/\n.. _blog post: https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/\n.. _structured concurrency: https://en.wikipedia.org/wiki/Structured_concurrency\n.. _SC: https://en.wikipedia.org/wiki/Structured_concurrency\n.. _libdill-docs: https://sustrik.github.io/libdill/structured-concurrency.html\n.. _unrequirements: https://en.wikipedia.org/wiki/Actor_model#Direct_communication_and_asynchrony\n.. _async generators: https://www.python.org/dev/peps/pep-0525/\n.. _trio-parallel: https://github.com/richardsheridan/trio-parallel\n.. _uv: https://docs.astral.sh/uv/\n.. _msgspec: https://jcristharif.com/msgspec/\n.. _guest: https://trio.readthedocs.io/en/stable/reference-lowlevel.html?highlight=guest%20mode#using-guest-mode-to-run-trio-on-top-of-other-event-loops\n\n\n.. |gh_actions| image:: https://img.shields.io/endpoint.svg?url=https%3A%2F%2Factions-badge.atrox.dev%2Fgoodboy%2Ftractor%2Fbadge\u0026style=popout-square\n    :target: https://actions-badge.atrox.dev/goodboy/tractor/goto\n\n.. |docs| image:: https://readthedocs.org/projects/tractor/badge/?version=latest\n    :target: https://tractor.readthedocs.io/en/latest/?badge=latest\n    :alt: Documentation Status\n\n.. |logo| image:: _static/tractor_logo_side.svg\n    :width: 250\n    :align: middle\n","funding_links":[],"categories":["Python"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgoodboy%2Ftractor","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgoodboy%2Ftractor","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgoodboy%2Ftractor/lists"}