Channels & packs

Channels & packs

Every way of talking to GUS is a channel, and every channel runs the same gauntlet: secret-guard, Shield scan, classifier, spend caps, receipts. No channel is a bypass. Packs add capabilities — Gmail, Mac control, release publishing — under the same rules.

Telegram

The pocket channel: job requests, approval cards with Approve / Deny, and /stop / /resume. Connect it in the setup wizard by creating a bot with @BotFather and pasting the token into the panel — never into a chat. Telegram chats are not end-to-end encrypted, so GUS only ever sends approvals and summaries there — never secrets.

Panel chat

The control panel's Chat view is one more channel, like Telegram: what you type is screened for secrets, scanned by the Agent-Shield gate, classified, metered against your caps, and settled with a receipt. The conversation is kept in durable state on the machine, and when a chat job parks for approval, the card appears right in the conversation. Details in Settings, explained. Voice is a channel too.

Delegates — letting another agent ask GUS for work

GUS normally serves one human in one private chat. A delegate is a second, named agent identity that may request work in a shared conversation — for setups where another agent hands GUS jobs while the operator watches the same room.

OperatorDelegateAnyone else
Submit job requestsYesYesIgnored
Approve / denyYesNoNo
/stop, /resume, /updateYesNoNo

A delegate's jobs run under the same classifier, Agent-Shield gate, and spend caps as the operator's. Anything that would park for approval parks for the operator — a delegate can never approve, not even its own jobs (in Permissive mode, agent approvers exist, but under their own limits — see approval delegation). Every delegate request is prefixed with its origin ([from <name>] …) in the job record and the runtime prompt, so the receipts and the model both know who asked.

Configuration lives in ~/.gus/state/delegates.json, re-read per message so edits land with no restart:

{
  "conversations": {
    "-1001234567890": {
      "operatorId": "1234567890",
      "delegates": [{ "senderId": "987654321", "name": "Helper" }]
    }
  }
}

Keys are channel conversation ids (Telegram chat ids; group ids are negative). A conversation with no entry behaves exactly as GUS always has — the private-chat model, where the only possible sender is the operator. In a Telegram group, both bots need Group Privacy OFF in BotFather, approval buttons render in the group but only the operator's tap resolves (any other tap is refused and logged), and strangers' messages are ignored and logged.

The bridge inbox

Telegram does not deliver bot messages to other bots, in any configuration. A bot delegate therefore cannot reach GUS through the group chat. The last hop is a file: the delegate's tooling appends one JSON file per request to ~/.gus/state/bridge-inbox/, and the control plane's bridge adapter turns each file into the same channel event a chat message would have produced. Request file shape: { "id": "<unique>", "text": "<request>", "ts": <ms> }, named so lexical order is chronological. Replies, receipts, and approval buttons still post to the conversation through Telegram — the group remains the room the operator watches; only the delegate's inbound hop is a file.

The Gmail pack

GUS's first capability pack: Gmail, executed entirely in the control plane. (Calendar is a separate later pack — nothing here touches it.) The agent can search, read, and summarize mail; draft; send; reply; label and archive; delete (trash or permanent); report spam; and block senders — all through the same Shield gate, classifier, Approval Engine, and receipts, plus two pack-specific content rules:

Every action verifies its post-condition against the mailbox (the sent message exists in the right thread, the labels actually moved, the deleted message is actually gone) and ends verified-done or failed-loudly.

Once / Always / Never

SettingMechanismMeaning
Once (default)Approval EngineOne exact action, hash-bound to its arguments, single use. Change a recipient, a subject, one message ID — the approval no longer matches and the action parks again.
AlwaysStanding approvals in policy.jsonA standing approval scoped to an action class (gmail-send, gmail-reply, gmail-modify) and, for sends, to a person, a domain, or an operator-named group.
NeverA deny rule in policy.jsonDeny rules are matched before everything else. Never beats Always, always.

The hard lines (code, not policy — no file can soften them)

Setup and revoking

The OAuth credential lives in the vault under the name gmail-oauth and never enters the agent's context. Setup is a one-time operator job: create a Google Cloud project with the Gmail API enabled, configure an OAuth consent screen (set publishing status to In production — in Testing status Google expires refresh tokens after 7 days), create a Desktop-app OAuth client, and store the resulting credential JSON in the vault with gmail-admin. Revoking is one step either side: gmail-admin revoke removes the credential from the vault, or revoke GUS on the Google account's third-party access page to kill the refresh token itself. Gmail actions ride each job's Governor caps like every other tool; the pack introduces no separate spend meter.

Status, stated honestly: the pack is unit-proven against a mock transport and a fake HTTP layer (token exchange, message parsing, MIME send). A first run against a live mailbox is the remaining proof step.

Publishing releases (v0.9.5)

GUS can publish its own app releases to GitHub: the pack attaches a built DMG and its checksum to an existing draft release, then publishes it. The rules it runs under:

The publish credential is a fine-grained GitHub token in the vault (repository access limited to the GUS repo, Contents: Read and write). It is used only inside the control plane and never crosses a process boundary — there is deliberately no script route to it. Revocation is two-sided, as with every pack: remove the vault secret, or revoke the token on GitHub.