AGENTS.md 4.1 KB

Agent Guidelines for hermes-chatto-plugin

Tests

adapter.py imports from gateway.*, which lives in the Hermes Agent rather than this repo. Without its source on the PYTHONPATH, collection dies with ModuleNotFoundError: No module named 'gateway' before a single test runs. So fetch the Hermes Agent and keep it around before running the tests:

git clone https://github.com/NousResearch/hermes-agent.git /tmp/hermes
PYTHONPATH=/tmp/hermes uv run --with pyyaml pytest -q

gateway.config needs pyyaml, which is not among our dev dependencies. If a checkout already exists somewhere, point at that path instead of cloning again.

Typed access

Work against the declared types. Do not reach for values dynamically when a typed attribute exists.

chattolib ships dataclasses with py.typed for everything it returns — Message, Asset, AssetUpload, Room, User. Read their fields directly, so a wrong name is a loud AttributeError at the first call and a type checker can see it before that:

upload_id = upload.upload_id            # yes
upload_id = str(getattr(cast(Any, upload), "id", ""))   # no

The second form cost us every single file upload. AssetUpload has no id field — it is called upload_id — but cast(Any, ...) disables the check and the "" default turns the mismatch into an empty string. The result was an error message blaming the server (CreateUpload returned no upload ID) for a name we got wrong ourselves, and it survived in production because it never raised.

So: no cast(Any, ...) on a value whose type is known, and no getattr with a fallback default to paper over a field you are not sure about — look the field up in vendor/common/chattolib/types.py instead. getattr is fine for genuinely optional or duck-typed things (a handler that may not be set, a payload from an untyped source), not for dodging a type.

Tests should exercise real library types too, not mocks shaped like them. The upload bug was invisible because the tests replaced _upload_asset wholesale with an AsyncMock, so no test ever touched a real AssetUpload.

Capabilities

When you extend what the adapter can do, keep the startup banner current.

_capabilities() in adapter.py logs what the plugin supports at registration, so starting it up tells you what is available instead of making you read the source. Whether a capability counts is derived from real overrides: it only counts when ChattoAdapter replaces the BasePlatformAdapter method. The label for it, however, lives in _CAPABILITY_LABELS and is not discovered automatically.

So when you add an overridden BasePlatformAdapter method:

  1. Add an entry to _CAPABILITY_LABELS (method name → wording that means something to someone who has not read the code). Skip this and the plugin can do the thing but says so nowhere.
  2. Check whether the platform_hint in register() needs to mention it. That hint goes into the system prompt and is the only way the model learns what the channel can do — without it the agent falls back to shelling out.
  3. Add a test in TestRegistration if the capability is user-visible.

The reverse holds too: drop a method and its line disappears from the log on its own, but its _CAPABILITY_LABELS entry should go with it.

Shell Scripts

Always run shellcheck after editing any shell scripts.

shellcheck path/to/script.sh

Or validate all shell scripts in the project:

find . -name "*.sh" -exec shellcheck {} \;

Configuration

Project-specific shellcheck rules are defined in .shellcheckrc. Default severity is error to catch all issues.

Why

  • Prevents syntax errors and common pitfalls (e.g., missing quotes, unsafe variable expansions)
  • Ensures portability across different shell environments
  • Maintains code quality and security standards

Integration

Consider adding a pre-commit hook for automatic validation:

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/koalaman/shellcheck-precommit
    rev: v0.9.0
    hooks:
      - id: shellcheck
        args: [--severity=error]