{"id":51033697,"url":"https://github.com/launchapp-dev/animus-plugin-sdk-py","last_synced_at":"2026-06-22T03:01:48.868Z","repository":{"id":363243451,"uuid":"1252841674","full_name":"launchapp-dev/animus-plugin-sdk-py","owner":"launchapp-dev","description":"Python SDK for authoring Animus plugins (subject backends, providers, triggers, transports, log storage)","archived":false,"fork":false,"pushed_at":"2026-06-08T03:12:01.000Z","size":132,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-08T05:09:00.023Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://animus-docs.vercel.app","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/launchapp-dev.png","metadata":{"files":{"readme":"README.md","changelog":null,"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,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-28T23:34:55.000Z","updated_at":"2026-06-08T03:12:06.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/launchapp-dev/animus-plugin-sdk-py","commit_stats":null,"previous_names":["launchapp-dev/animus-plugin-sdk-py"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/launchapp-dev/animus-plugin-sdk-py","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/launchapp-dev%2Fanimus-plugin-sdk-py","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/launchapp-dev%2Fanimus-plugin-sdk-py/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/launchapp-dev%2Fanimus-plugin-sdk-py/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/launchapp-dev%2Fanimus-plugin-sdk-py/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/launchapp-dev","download_url":"https://codeload.github.com/launchapp-dev/animus-plugin-sdk-py/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/launchapp-dev%2Fanimus-plugin-sdk-py/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34632723,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-22T02:00:06.391Z","response_time":106,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":[],"created_at":"2026-06-22T03:01:47.938Z","updated_at":"2026-06-22T03:01:48.856Z","avatar_url":"https://github.com/launchapp-dev.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# animus-plugin-sdk (Python)\n\nPython SDK for authoring [Animus](https://github.com/launchapp-dev/animus-cli)\nstdio plugins. Pydantic-typed, with the Rust protocol crates as the single\nsource of truth. Covers every plugin role: subject, provider, trigger,\ntransport, log-storage, queue, workflow-runner, durable-store, memory-store,\nand notifier.\n\nThis is the Python parallel to the TypeScript SDK\n(`launchapp-dev/animus-plugin-sdk-ts`). Both SDKs **generate** their wire types\nfrom the same source-of-truth JSON Schemas published by the\n[`launchapp-dev/animus-protocol`](https://github.com/launchapp-dev/animus-protocol)\nrepo (the TS SDK emits Zod schemas; this SDK emits pydantic v2 models).\n\n## Install\n\n```sh\npip install animus-plugin-sdk\n```\n\n## Hello world (subject backend)\n\n```python\n# my_plugin.py\nfrom animus_plugin_sdk import (\n    PluginKind,\n    Subject,\n    SubjectCallContext,\n    SubjectListParams,\n    SubjectListResult,\n    define_plugin,\n)\n\n\nclass HelloBackend:\n    def list(self, params: SubjectListParams, ctx: SubjectCallContext) -\u003e SubjectListResult:\n        return SubjectListResult(\n            subjects=[Subject(id=\"task:1\", kind=ctx.kind, title=\"hello\",\n                              status=\"ready\", created_at=\"\", updated_at=\"\")],\n            fetched_at=\"\",\n        )\n\n    def get(self, params, ctx):\n        if params.get(\"id\") == \"task:1\":\n            return Subject(id=\"task:1\", kind=ctx.kind, title=\"hello\",\n                           status=\"ready\", created_at=\"\", updated_at=\"\")\n        return None\n\n\nif __name__ == \"__main__\":\n    define_plugin(\n        kind=PluginKind.SUBJECT_BACKEND,\n        impl=HelloBackend(),\n        name=\"hello-subjects\",\n        version=\"0.1.0\",\n        description=\"Hard-coded sample backend\",\n        subject_kinds=[\"task\"],\n        env_required=[\"MY_API_TOKEN\"],\n    ).run()\n```\n\nRun the plugin to drive the JSON-RPC loop on stdin/stdout (`python my_plugin.py`)\nor print the discovery manifest (`python my_plugin.py --manifest`). The SDK\nauto-fills the wire-mandatory `status` / `created_at` / `updated_at` fields when\na hello-world example omits them.\n\n## Layered submodule structure\n\nThe top-level `animus_plugin_sdk` keeps the back-compat surface: `define_plugin`,\nthe base/runtime layer (wire, handshake, error codes, `PROTOCOL_VERSION`), and\nevery role contract. Each role is **also** importable as a submodule mirroring\nthe Rust crates, exposing that role's contract + generated pydantic types:\n\n```python\nfrom animus_plugin_sdk.subject import SubjectBackend, ensure_wire_subject\nfrom animus_plugin_sdk.subject import gen as subject_types     # generated wire types\nfrom animus_plugin_sdk.provider import Provider, AgentRunRequest, AgentStream\nfrom animus_plugin_sdk.trigger import TriggerBackend, TriggerEvent\nfrom animus_plugin_sdk.transport import TransportBackend, TransportConfig\nfrom animus_plugin_sdk.log_storage import LogStorageBackend, LogEntry\nfrom animus_plugin_sdk.queue import Queue, QueueEnqueueRequest\nfrom animus_plugin_sdk.workflow_runner import WorkflowRunner\nfrom animus_plugin_sdk.durable_store import DurableStore\nfrom animus_plugin_sdk.memory_store import MemoryStore\nfrom animus_plugin_sdk.notifier import Notifier\n```\n\n| Layer                              | What it holds                                                   |\n| ---------------------------------- | --------------------------------------------------------------- |\n| `animus_plugin_sdk`                | `define_plugin`, base/runtime, all role contracts (back-compat) |\n| `animus_plugin_sdk.\u003crole\u003e`         | role contract (Protocol/ABC) + generated pydantic types (`gen`) |\n| `animus_plugin_sdk.types`          | base layer: `PROTOCOL_VERSION`, `PluginKind`, `ErrorCode`, envelopes |\n| `animus_plugin_sdk.types.generated`| one pydantic module per protocol crate (codegen output)         |\n\n## Roles (full coverage)\n\nEvery role from the protocol spec §7 is wired. The dispatcher validates inbound\nparams against the generated pydantic models (returning `-32602` `invalid_params`\non failure) and advertises only the methods it can serve.\n\n| Role                  | Methods wired                                                                                             |\n| --------------------- | --------------------------------------------------------------------------------------------------------- |\n| `subject_backend`     | `subject/list`, `subject/get`, `subject/schema`; optional `create`/`update`/`status`/`next`/`delete`; legacy `\u003ckind\u003e/*` routes |\n| `provider`            | `agent/run`, `agent/resume`, `agent/cancel` + streaming `agent/output\\|thinking\\|toolCall\\|toolResult\\|error` |\n| `trigger_backend`     | `trigger/watch` (streams flat `trigger/event`), `trigger/schema`, optional `trigger/ack`                  |\n| `transport_backend`   | `transport/start`, `transport/shutdown`, `transport/schema`                                               |\n| `log_storage_backend` | `log_storage/store`, optional `log_storage/query`, streaming `log_storage/tail`, `log_storage/schema`     |\n| `queue`               | `queue/enqueue\\|list\\|lease\\|stats\\|hold\\|release\\|drop\\|mark_assigned\\|completion\\|reorder`, optional `release_pending` |\n| `workflow_runner`     | `workflow/execute`, `workflow/run_phase`                                                                  |\n| `durable_store`       | `durable/begin_workflow_run\\|begin_step\\|commit_step\\|abandon_step\\|recover_in_flight\\|query_run`         |\n| `memory_store`        | `memory/put\\|get\\|query\\|list_scopes\\|delete_scope`                                                       |\n| `notifier`            | `notifier/notify`, optional `notifier/flush`, `notifier/schema`                                           |\n\nOptional methods that an impl does not provide return `-32001`\n(`method_not_supported`); unrecognized methods return `-32601`\n(`method_not_found`). This matches the TypeScript SDK's role surface exactly.\n\n### Streaming concurrency\n\nThe stdio wire read loop is synchronous and serial. Streaming roles\n(`trigger/watch`, `log_storage/tail`) acknowledge immediately and then drain the\nauthor's iterator on a **background daemon thread**, emitting notifications via\nthe wire. Every stdout frame is written + flushed under a lock, so a background\nnotification never interleaves bytes with the main loop's response — the\nsynchronous subject path and per-frame framing are untouched. Provider streaming\nruns inline on the dispatch thread: the impl emits via `ctx.stream` before\nreturning the final `AgentRunResponse`.\n\n## Generated wire types (Rust is the source of truth)\n\nThe wire payload types under `animus_plugin_sdk.types.generated/\u003ccrate\u003e.py` are\n**generated** by `scripts/codegen.py` from the vendored JSON Schema bundles in\n`schemas/\u003ccrate\u003e/_all.json` (copied from `animus-protocol`). They are pydantic v2\nmodels with `extra=\"allow\"` so unknown fields round-trip (the Python equivalent\nof Rust's `Other(String)` fall-through). Open-string enums (`TriggerActionHint`,\n`TriggerAckStatus`) stay `str`; JSON-RPC envelope fields\n(`id`/`params`/`result`/`payload`/`data`) stay `Any`.\n\nRegenerate after updating the vendored schemas:\n\n```sh\npython scripts/codegen.py        # regenerate the models\npython scripts/codegen_check.py  # CI drift check (fails on uncommitted diff)\n```\n\nA field whose name shadows a pydantic `BaseModel` attribute (e.g. `schema`) is\nemitted as `\u003cname\u003e_` with a pydantic `alias`, so the wire name round-trips.\n\n## Protocol version\n\nThis SDK targets `PROTOCOL_VERSION = \"1.1.0\"`. The handshake validates the host's\nadvertised version with strict major-version match: a `1.x` plugin accepts any\n`1.x` host but rejects `0.x` or `2.x`. The v1.1.0 changes are additive — the four\nnew plugin kinds (`workflow_runner`, `queue`, `durable_store`, `memory_store`,\nplus `notifier`), the tolerated `init_extensions` on `initialize`, and the\n`kind_capabilities` map emitted only for the new kinds (v1.0.0 kinds keep the\nwire shape byte-identical). v1.0.0 hosts continue to work.\n\n## Parity with the TypeScript SDK\n\n| Concept             | TS                     | Python                          |\n| ------------------- | ---------------------- | ------------------------------- |\n| Entrypoint          | `definePlugin(spec)`   | `define_plugin(kind, impl, …)`  |\n| Stdio loop          | `createWire()`         | `create_wire()`                 |\n| Handshake helpers   | `buildManifest`        | `build_manifest`                |\n| Role contracts      | `interface`            | `typing.Protocol`               |\n| Wire payload models | generated Zod schemas  | generated pydantic models       |\n| Param validation    | `schema.safeParse`     | `model.model_validate`          |\n| Subpath exports     | `/subject`, `/provider`| `animus_plugin_sdk.subject`, …  |\n| Streaming           | async iterators        | sync iterators + daemon thread  |\n\n## Development\n\n```sh\ngit clone https://github.com/launchapp-dev/animus-plugin-sdk-py.git\ncd animus-plugin-sdk-py\npython3.11 -m venv .venv\nsource .venv/bin/activate\npip install -e \".[dev]\"\npython scripts/codegen_check.py\nruff check\nruff format --check\nmypy animus_plugin_sdk\npytest -v\n```\n\n## License\n\nElastic-2.0\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flaunchapp-dev%2Fanimus-plugin-sdk-py","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flaunchapp-dev%2Fanimus-plugin-sdk-py","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flaunchapp-dev%2Fanimus-plugin-sdk-py/lists"}