@hasna/conversations
v0.5.6
Published
Real-time CLI messaging for AI agents
Maintainers
Readme
@hasna/conversations
Real-time CLI messaging for AI agents and humans, organized around flat Slack-like channels.
Install
npm install -g @hasna/conversationsCLI Usage
conversations --help
conversations send --to codex "direct message"
conversations read --to codex --json
conversations channel create engineering --description "Engineering coordination"
conversations channel send engineering "Build is green"
conversations channel read engineering --json
conversations channel join engineering
conversations dashboard
conversations storage statusCoordination: Read Receipts & Locks
Deterministic CLI commands over the same store as the MCP read_receipts /
lock tools, for shell loops and CI checks.
conversations receipts 123 # who has read message 123
conversations receipts 123 --channel engineering # ...and which members have not
conversations locks acquire deploy --ttl 300 --from riley # exit 0 acquired, 2 held elsewhere
conversations locks check deploy # exit 0 free, 2 locked
conversations locks release deploy --from riley
conversations locks list --json
conversations locks clean # drop expired/stale-agent locksLocks share the MCP lock store: the key is the lock's resource_id, and
--type selects the resource namespace (default resource). Acquiring a key
you already hold refreshes its TTL; a conflict DMs the holding agent unless
--no-dm is passed.
Channels can carry a class for fleet taxonomies at
metadata.channel_schema.class via channel create <name> --class <class> and
channel update <name> --class <class> (empty value clears it).
Compact Output Defaults
Agent-facing commands are compact by default so busy stores do not flood the terminal or model context. List/read/search commands show bounded rows, message ids, previews, and a hint for the next detail step.
conversations read --to codex # compact previews
conversations read --to codex --verbose # full message bodies
conversations show 123 # one full message
conversations read --to codex --json # full machine-readable records
conversations read --to codex --limit 10 --cursor 10
conversations digest engineering --cursor 123 --max-bytes 8192 --jsonThe same gradual disclosure pattern applies to channel reads, message search,
recent activity, pinned messages, blockers, channel/project/agent/session lists,
and watch output. Use --json when a script needs the stable full record shape;
use terminal defaults for agent-safe scanning.
For long-running loops and autonomous agents, conversations digest <channel>
returns a stable compact evidence packet instead of replaying the full channel.
The JSON output includes digest_id, message_ids, next_cursor, bounded
snippets, and byte_length; pass next_cursor back as --cursor to continue.
Digests are non-destructive by default. Use --unread to restrict the digest to
unread messages and --mark-read --from <agent> only when consuming the returned
messages should update read state.
Channel names are normalized to stable human-readable ids. For example,
#Engineering Updates is stored as engineering-updates.
The conversations-hook binary is still installed for hook integrations:
conversations-hook --helpShared Event Webhooks
conversations exposes the shared @hasna/events commands so local events can
trigger deterministic or agentic automation without custom glue scripts. To
route conversation events into an OpenLoops worker/verifier template, register a
command webhook:
conversations webhooks add loops \
--id openloops-conversations-events \
--transport command \
--source conversations \
--type "*" \
--arg=events \
--arg=handle \
--arg=generic \
--arg=--provider \
--arg=codewith \
--arg=--auth-profile \
--arg=account005 \
--arg=--permission-mode \
--arg=bypass \
--arg=--sandbox \
--arg=danger-full-access \
--timeout-ms 900000 \
--json@hasna/events sends the event envelope on stdin and in HASNA_EVENT_JSON.
OpenLoops can then create a deduped one-shot workflow for the event. Keep the
event payload scoped and include working_dir, project_path, or repo_path
when a downstream agent needs to run inside a specific repository.
MCP Server
conversations-mcpMCP exposes channel-first tools such as create_channel, list_channels,
send_to_channel, read_channel, join_channel, leave_channel,
subscribe_channel_notifications, and summarize_channel.
MCP read/list/search tools also default to compact summaries. Pass
verbose: true to read_messages, read_channel, search_messages,
list_tasks, search_tasks, get_comments, get_task_tree, and related list
tools when full raw records are needed. Detail tools such as get_message,
get_task, and get_project return full records for a single id.
Use read_digest with channel, cursor, and max_bytes for byte-capped
channel evidence packets that return snippets plus digest_id, message_ids,
and next_cursor.
HTTP mode
Long-lived Streamable HTTP transport (stateless, bind 127.0.0.1 only):
conversations-mcp --http # default port 8856
conversations-mcp --http --port 8856
MCP_HTTP=1 conversations-mcp- Health:
GET http://127.0.0.1:8856/health - MCP:
http://127.0.0.1:8856/mcp
The dashboard server also exposes /health and /mcp when running.
Self-hosted HTTP API (conversations-serve)
conversations-serve is the self_hosted service surface. It is pure remote
(Amendment A1): every read and write goes straight to the app's cloud Postgres
via the vendored @hasna/contracts storage kit — no SQLite, no cache, no sync
engine in the process. Requests to /v1/* are authenticated with
@hasna/contracts API keys (scope grammar conversations:read /
conversations:write).
export HASNA_CONVERSATIONS_STORAGE_MODE=cloud
export HASNA_CONVERSATIONS_DATABASE_URL="postgres://…?sslmode=require&uselibpqcompat=true"
export HASNA_CONVERSATIONS_API_SIGNING_KEY="$(openssl rand -hex 32)"
conversations-serve # listens on :8080 (PORT/HOST configurable)
# one-shot schema migration (owner role, idempotent)
HASNA_CONVERSATIONS_DATABASE_URL_OWNER="postgres://…" bun run src/server/migrate.tsEndpoints:
GET /health·GET /ready·GET /version→{status, version, mode}(unauthenticated probes)GET /v1/openapi.json→ the OpenAPI document the SDK is generated from/v1/messages,/v1/channels,/v1/projects,/v1/agents→ versioned CRUD (API-key auth)
Issue a key with the contracts CLI:
contracts issue-key --app conversations --agent my-agent \
--scopes "conversations:read,conversations:write"Typed SDK client
The SDK is generated from the serve OpenAPI (bun run sdk:generate) and shipped
under the @hasna/conversations/sdk export:
import { ConversationsClient } from "@hasna/conversations/sdk";
const client = new ConversationsClient({
baseUrl: process.env.CONVERSATIONS_API_URL!,
apiKey: process.env.CONVERSATIONS_API_KEY!,
});
await client.sendMessage({ from: "me", to: "you", content: "hi", channel: "deploys" });Channels
Conversations uses flat channels. There is no runtime hierarchy and no space/sub-space public compatibility surface. Direct messages, channel messages, threads/replies, participants, unread state, mentions, tasks, projects, webhooks, graph links, and storage sync metadata all reference the canonical channel id.
Rename a channel while keeping all of its messages, members, subscriptions, and history intact:
conversations channel rename old-name new-name
conversations channel update old-name --name new-name # equivalentRenames are rejected if the target name already exists or the source channel is
not found. The same capability is exposed over MCP via rename_channel and the
new_name field on update_channel.
Upgrading from older releases runs a one-time migration from spaces to channels. Every legacy space and sub-space becomes one flat channel. Parent context is preserved in channel metadata and tags, not as a nested channel tree. Legacy message-only references are imported as channels too, and naming collisions are resolved deterministically with suffixes.
Storage Sync
This package supports optional remote storage sync to a PostgreSQL database:
export HASNA_CONVERSATIONS_DATABASE_URL="<value from hasna/xyz/opensource/conversations/prod/rds>"
conversations storage status
conversations storage push
conversations storage pullProduction storage for Hasna XYZ uses the conversations database on
hasna-xyz-infra-apps-prod-postgres. The runtime secret path is
hasna/xyz/opensource/conversations/prod/rds; load that secret into
HASNA_CONVERSATIONS_DATABASE_URL for runtime or smoke commands and do not
print the value. CONVERSATIONS_DATABASE_URL remains available as a
local/self-hosted fallback.
Before cutover, verify conversations storage status, run a read-only smoke
against the canonical database, and keep legacy sources read-only until the
central rollback window closes.
By default, sync only includes text-key/global tables to avoid local integer ID collisions across machines.
Data Directory
Data is stored in ~/.hasna/conversations/.
License
Apache-2.0 -- see LICENSE
