Posting a live message
A plugin can post one message into the conversation it is working for and update that same message later, on any channel, with no per-platform code. Use it for live progress (a checklist while a long tool runs, a plan card that changes as the plan is revised): Signatures: ctx.post_message(text: str) -> PluginMessage and PluginMessage.edit(text: str) -> dict . From async code that already runs on the gateway's event loop (an async hook or slash-command handler) use await ctx.apost_message(text) and await handle.aedit(text) instead; the sync forms refuse there with error: "on_event_loop" rather than deadlocking the loop. Which conversation. The current conversation is the session bound to the calling code: a tool handler or hook running in that session's turn, its slash command, or a hook fired for it. The plugin never names a chat, so it cannot post anywhere else. A thread the plugin starts itself does not inherit that binding (start it with contextvars.copy_context().run(...) if it should), and a timer, a cron run or a process with no live gateway has no conversation at all. Never raises. Every call returns a result. handle.ok , handle.message_id and handle.error describe the last…
From the official Hermes Agent documentation.
def run_migration(args, **kwargs):
status = ctx.post_message("Migrating: 0/3 tables")
for i, table in enumerate(TABLES, 1):
migrate(table)
status.edit(f"Migrating: {i}/3 tables")
status.edit("Migration done ✓")
return tool_result({"migrated": len(TABLES)})