{"id":51417677,"url":"https://github.com/offendingcommit/hermes-plugin-kit","last_synced_at":"2026-07-04T21:30:21.623Z","repository":{"id":364396734,"uuid":"1267716496","full_name":"offendingcommit/hermes-plugin-kit","owner":"offendingcommit","description":"A @tool decorator for hermes-agent plugins: convention-correct LLM tool schemas, validation, logging, and the JSON envelope — baked in.","archived":false,"fork":false,"pushed_at":"2026-06-12T21:05:04.000Z","size":25,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-12T22:24:57.945Z","etag":null,"topics":["ai-agents","decorator","hermes-agent","json-schema","llm","llm-tools","plugin-development","python","tool-calling"],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/offendingcommit.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-06-12T19:52:59.000Z","updated_at":"2026-06-12T21:05:08.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/offendingcommit/hermes-plugin-kit","commit_stats":null,"previous_names":["offendingcommit/hermes-plugin-kit"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/offendingcommit/hermes-plugin-kit","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/offendingcommit%2Fhermes-plugin-kit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/offendingcommit%2Fhermes-plugin-kit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/offendingcommit%2Fhermes-plugin-kit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/offendingcommit%2Fhermes-plugin-kit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/offendingcommit","download_url":"https://codeload.github.com/offendingcommit/hermes-plugin-kit/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/offendingcommit%2Fhermes-plugin-kit/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35136712,"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-07-04T02:00:05.987Z","response_time":113,"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":["ai-agents","decorator","hermes-agent","json-schema","llm","llm-tools","plugin-development","python","tool-calling"],"created_at":"2026-07-04T21:30:21.103Z","updated_at":"2026-07-04T21:30:21.617Z","avatar_url":"https://github.com/offendingcommit.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# hermes-plugin-kit\n\n\u003e A `@tool` decorator for [hermes-agent](https://github.com/NousResearch/hermes-agent) plugins — convention-correct LLM tool schemas, validation, logging, and the JSON envelope, baked in.\n\n[![test](https://github.com/offendingcommit/hermes-plugin-kit/actions/workflows/test.yml/badge.svg)](https://github.com/offendingcommit/hermes-plugin-kit/actions/workflows/test.yml)\n![python](https://img.shields.io/badge/python-3.13%2B-blue)\n\n`hermes-plugin-kit` is a tiny, dependency-free helper for authoring **tools** in\n[hermes-agent](https://github.com/NousResearch/hermes-agent) plugins. Decorate a\nhandler with `@tool`, register it with `register_all`, and the LLM-facing schema,\nargument validation, structured logging, and the JSON result envelope are all\ngenerated for you — correctly, every time.\n\n## Motivation\n\nThis began as a one-line fix. A hermes plugin had shipped its tool arguments at the\ntop level of the schema instead of under `parameters`; the model received a tool with\nno arguments and couldn't call it until someone opened the plugin's source to find the\nfield names. The fix was trivial — but the same mistake was latent in every other\nplugin that hand-rolls its schemas, envelopes, and logging.\n\nFixing them one at a time and hoping the next author remembers the rules doesn't scale.\nSo we pulled the conventions out into a single, reusable kit. Get them right once, here,\nand every plugin that reaches for `@tool` inherits them — and the next person reading a\ntool's logs can actually see what went wrong.\n\n## Why it exists\n\nHermes turns each tool into an OpenAI-style function: `{\"type\": \"function\",\n\"function\": {**schema, \"name\": ...}}`. That means a tool's arguments **must** live\nunder a `parameters` key. It's a small detail with an outsized failure mode, and\nhand-written plugins keep tripping over the same things:\n\n- **Empty `{}` arguments.** Put `properties` at the top level instead of under\n  `parameters` and the model receives a tool with *no arguments* — it can't tell\n  what to pass, and silently guesses wrong field names until it gives up.\n- **`TypeError: unhashable type: 'slice'`.** Return a `dict` instead of a JSON\n  string and the framework crashes downstream.\n- **Silent failures.** A terse error with no log leaves operators staring at\n  \"invalid input\" with no idea what the model actually sent.\n\nThese aren't exotic — they're the *default* mistakes when every plugin re-implements\nthe same boilerplate. `hermes-plugin-kit` makes them structurally impossible:\n\n- **Schema convention** — arguments are always nested under `parameters`.\n- **Self-documenting** — required argument names and examples are appended to the\n  description, the one field a model reliably sees.\n- **Validation + instructive errors** — a missing or blank required argument returns\n  an error that *names the argument and its example*.\n- **Explicit tool namespacing** — build names with `tool_name(namespace, verb, noun)`\n  and reject Hermes agent-loop names such as `memory`.\n- **Logging** — `WARNING` on a rejected call (arguments truncated, secret-looking\n  values redacted), `INFO` on success, under your plugin's own logger.\n- **Envelope + safety** — return a plain `dict` (or raise); the kit encodes the JSON\n  string, catches exceptions, and always returns `str` from an `(args, **kwargs)`\n  handler.\n\n## Who it's for\n\nAnyone writing or maintaining a hermes-agent plugin who wants their tools to be\ncorrect and debuggable without copy-pasting the same schema/envelope/logging\nscaffolding into every file. It pairs naturally with the hermes plugin conventions\nand adds nothing to your runtime footprint — pure standard library.\n\n## Install\n\nThe package is consumed straight from Git (works great with [uv](https://docs.astral.sh/uv/)):\n\n```bash\nuv add git+https://github.com/offendingcommit/hermes-plugin-kit.git\n# or\npip install git+https://github.com/offendingcommit/hermes-plugin-kit.git\n```\n\nWith uv, pin it as a source in your plugin's `pyproject.toml`:\n\n```toml\ndependencies = [\"hermes-plugin-kit\"]\n\n[tool.uv.sources]\nhermes-plugin-kit = { git = \"https://github.com/offendingcommit/hermes-plugin-kit.git\", branch = \"main\" }\n```\n\n## Usage\n\n`tools.py`:\n\n```python\nfrom hermes_plugin_kit import tool, tool_name, register_all, str_arg, int_arg\n\n@tool(\n    toolset=\"messaging\",\n    namespace=\"discord\",\n    name=tool_name(\"discord\", \"read\", \"thread\"),\n    requires_env=[\"DISCORD_BOT_TOKEN\"],\n    params={\n        \"thread_id_or_url\": str_arg(\n            \"Discord thread link or numeric ID\",\n            required=True, example=\"123456789012345678\",\n        ),\n        \"limit\": int_arg(\"Messages to return\", minimum=1, maximum=100),\n    },\n)\ndef discord_read_thread(args, **kwargs):\n    \"\"\"Read recent messages from a Discord thread the bot can already access.\"\"\"\n    return {\"messages\": read_thread(args[\"thread_id_or_url\"], args.get(\"limit\", 100))}\n```\n\n`__init__.py`:\n\n```python\nfrom hermes_plugin_kit import register_all\nfrom . import tools\n\ndef register(ctx):\n    register_all(ctx, tools.__name__)\n```\n\nThat's it. `discord_read_thread` is registered with a `parameters`-wrapped schema, a\nself-documenting description, required-argument validation, logging, and the JSON\nenvelope — none of which you had to write.\n\n## Tool names\n\nHermes uses one global tool registry, and the agent loop intercepts core names\nbefore registry dispatch. Plugin tools should use an explicit domain namespace\nand an action verb:\n\n```python\nname=tool_name(\"discord\", \"read\", \"thread\")      # discord_read_thread\nname=tool_name(\"workspace\", \"write\", \"diary\")    # workspace_write_diary\nname=tool_name(\"workspace\", \"patch\", \"text\")     # workspace_patch_text\n```\n\nDo not register plugin tools with agent-loop names such as `memory`, `todo`,\n`session_search`, or `delegate_task`. The kit also rejects the reserved\n`memory_` prefix so plugin tools cannot be confused with Hermes' built-in\npersistent memory tool.\n\n## Argument specs\n\n- `str_arg(description, *, required=False, example=None, enum=None, min_length=None, **extra)`\n- `int_arg(description, *, required=False, example=None, minimum=None, maximum=None, **extra)`\n- `bool_arg(description, *, required=False, example=None, **extra)`\n- `arg(type, description, *, required=False, example=None, enum=None, **extra)` — generic\n\n`required` and `example` are kit metadata (stripped from the emitted JSON Schema, used\nfor validation, error text, and the self-documenting description). Any other keyword\npasses through verbatim into the JSON Schema for that property.\n\n## Handler contract\n\nA handler returns a `dict` (becomes the success `data`), or raises (becomes a tool\nerror), or returns a `str` as an escape hatch (treated as already-encoded JSON). It must\naccept `(args, **kwargs)` — runtime keys like `task_id`/`session_id` arrive as kwargs.\n\n## Development\n\nUses [uv](https://docs.astral.sh/uv/). Install it with `brew install uv` (macOS) or\n`curl -LsSf https://astral.sh/uv/install.sh | sh`.\n\n```bash\nmake install     # uv sync — create/sync the dev environment\nmake test        # uv run python -m unittest discover -s tests\nmake test-one T=tests.test_kit.SchemaConventionTests\nmake build       # uv build — wheel + sdist\n```\n\nCI runs `make test` on `actions/checkout@v6` + `astral-sh/setup-uv@v8.2.0` (Python 3.13).\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foffendingcommit%2Fhermes-plugin-kit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Foffendingcommit%2Fhermes-plugin-kit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foffendingcommit%2Fhermes-plugin-kit/lists"}