Skip to content
AFK-surfPublic

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

huddy

A heads-up display for a team's coding agents: one page showing every agent's goal, its current tasks, and the decisions it wants a human to look at. Agents report over MCP. A decision model (Jev) reviews what they post: it flags statements that are unclear, duplicated, overlapping or outdated, and tells the agent, which decides what to do. The model also ranks what the display shows, and files each task under the goal it serves, so the board lists every goal with its tasks under it. Teammates can answer decisions and comment on goals and tasks right on the board; the agent gets them with its next Huddy call.

  • Design: docs/DESIGN.md
  • Stack: Gleam on Erlang/OTP 29, mist + wisp, one JSON state file, no frontend framework (server-rendered HTML, zone swaps over SSE, ~150 lines of vanilla JS; documents are rendered in the browser with marked + DOMPurify).

Run it

Requirements: Erlang/OTP 29, Gleam 1.19, rebar3 (see .tool-versions).

cp huddy.example.json huddy.json        # edit: team, base_url, allowlist, email, decision model
export HUDDY_SECRET_KEY=$(openssl rand -base64 48)
export TYPESAFE_API_KEY=...             # or OpenRouter: see deploy/huddy.compose.json; or "enabled": false
gleam run -- --config huddy.json

For local development, use "base_url": "http://localhost:8080" and "email": {"from": "...", "transport": "log"}. Login codes are then printed to the log instead of being sent. Login codes are the only e-mail Huddy sends.

Docker Compose (Huddy + Mailpit)

cp deploy/.env.example .env      # HUDDY_SECRET_KEY, DECISION_MODEL_API_KEY (OpenRouter), allowed domain
docker compose up --build

Huddy runs at http://localhost:8080, and Mailpit (which catches the sign-in codes) at http://localhost:8025. The compose config is deploy/huddy.compose.json; it uses Jev through OpenRouter (typesafe/jev-1.13).

Docker

docker build -t huddy .
docker run -p 8080:8080 \
  -v huddy-data:/var/lib/huddy \
  -v $PWD/huddy.json:/etc/huddy/huddy.json:ro \
  -e HUDDY_SECRET_KEY -e TYPESAFE_API_KEY -e SMTP_PASSWORD \
  huddy

To keep the state in S3 instead of a volume, see State in S3.

Put Huddy behind a TLS-terminating reverse proxy. List the proxy in server.trusted_proxies so rate limits and the last-use IP recorded for MCP clients see real client IPs (taken from X-Forwarded-For), and have the proxy redact /mcp/k/… paths from its access logs (they contain secrets). The state volume must be a local POSIX filesystem (or use S3).

Huddy honours HTTPS_PROXY/NO_PROXY for its outbound calls (decision model, client metadata). For builds without GitHub access, or behind a TLS-intercepting proxy, see the comments at the top of the Dockerfile.

State in S3

Set storage.path to an S3 URL. Backups then go to the backups/ prefix next to it (or set storage.backup_dir, local or s3://):

"storage": {
  "path": "s3://acme-huddy/prod/huddy.json",
  "s3": { "region": "eu-west-1" }
}

Document files go to the documents/ prefix next to the state object (or set storage.documents_dir, local or s3://).

Credentials come from AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_SESSION_TOKEN, or from storage.s3.access_key_id etc. Region defaults to AWS_REGION. For MinIO, R2 and other S3-compatible stores, add "endpoint": "https://minio.internal:9000" (path-style addressing is then the default; override with path_style). Huddy needs s3:GetObject, s3:PutObject, s3:DeleteObject and s3:ListBucket. Details: DESIGN.md §5.7.

Connect an agent

Agents talk to https://<your huddy>/mcp (MCP Streamable HTTP; protocol versions 2026-07-28 and 2025-11-25).

  • OAuth: point the client at the URL and it signs in through your browser.

    claude mcp add --transport http huddy https://huddy.acme.dev/mcp

    For Codex, add this to ~/.codex/config.toml:

    [mcp_servers.huddy]
    url = "https://huddy.acme.dev/mcp"
  • Secret URL: create one in Settings (optionally pinned to an agent name and project, and optionally read-only), then use it as the server URL.

The server's instructions teach agents the protocol. They set goals (one per outcome), keep a few tasks open, post decisions when unsure (marked blocking if they have stopped), act on review findings or dismiss them with a reason, act on teammates' comments, and close items when done.

Comments and answers

On the board, anyone on the team can answer a decision (one click per option the agent listed, plus an optional reply) and comment on a goal or task. Any teammate's answer counts, not only the owner's. Closed items can't be commented on.

  • Comments reach the agent that owns the item at the top of its next Huddy tool result. An agent that has stopped to wait calls huddy_wait, which returns as soon as a comment or finding arrives (or after timeout_s, default 50 s); its owner can still answer in the session.
  • Comments, and review findings, repeat in every result until the agent acknowledges them with huddy_ack {ids: ["c7", "f88"]}, so a lost or truncated response loses nothing. huddy_inbox lists everything unacknowledged.
  • Agents reply in an item's thread with huddy_update {id, comment}, which can also close the item in the same call.
  • Each comment on the board shows whether it is still waiting, has been sent but not acknowledged, or has been acknowledged.

Limits: limits.comment_max_chars (default 10000), limits.comments_per_item (100) and limits.comments_per_user_per_minute (12). Details: DESIGN.md §17.

Documents

Agents can share longer Markdown (designs, reports, diagrams) as documents. A document is a zip archive with an index.md at its root plus the images it links to (![diagram](img/diagram.png)); plain Markdown works too. Documents are global: every agent and everyone on the team can list and read all of them, and any agent can replace or delete any of them.

  • huddy_doc_upload returns a one-time URL the agent uploads the document to: (cd my-doc && zip -r - .) | curl --data-binary @- -H 'Content-Type: application/zip' <url> (or a single .md file). The file never passes through the model. id uploads a new version of an existing document.
  • Document ids look like 12-design-notes: a number and a slug, which the agent picks with slug when it creates the document (else it comes from the title). The number alone (12) works too.
  • huddy_doc_delete deletes one.
  • huddy_doc_download returns a one-time link to the document's zip, exactly as uploaded, for curl -o 12-design-notes.zip <link>.
  • Replacing or deleting needs the document's current revision (a counter that goes up with every version), so an agent never overwrites a change it hasn't seen. Changes to one document run one at a time.
  • huddy_doc_list lists them; huddy_doc_read returns a document's raw index.md (revision, files and links are in the structured result).
  • Goals, tasks and decisions link documents with docs: ["12-design-notes"]; the board shows the links.
  • /docs lists them for humans; /docs/12-design-notes renders the Markdown in the browser. Humans can delete a document there.
  • Documents can be shared publicly, by a human from the document's page or by an agent with huddy_doc_share: anyone with the link (/shared/<token>) can read the document, and its later versions, without signing in, until someone stops sharing it. huddy_doc_read returns the link as public_url. Set "documents": {"public_links": false} to turn this off. Details: DESIGN.md §16.7.

Each version is stored as one zip, exactly as uploaded, on local disk or in S3 next to the state file (storage.documents_dir); the limit is limits.document_max_mb (default 20 MB unzipped, 200 files). Details: DESIGN.md §16.

Install as an app

Huddy is a progressive web app: Chrome, Edge and Android offer to install it from the address bar or menu, and Safari on iOS and macOS adds it with Add to Home Screen / Add to Dock. It then opens in its own window, and its notifications come from the app. The board itself always comes from the server; when the server can't be reached the app shows an offline page instead. Browsers only install sites served over HTTPS (or localhost).

Configuration

One JSON file. Every field is shown in huddy.example.json and explained in DESIGN.md §3. Any string value can be {"$env": "NAME"}.

Develop

gleam test                      # unit + end-to-end tests (boots the app on local ports)
gleam format --check src test
gleam build --warnings-as-errors

Fonts and scripts

The bundled fonts (Excalifont, Nunito) are licensed under the SIL OFL 1.1, see priv/static/FONTS-LICENSE.txt. The bundled Markdown renderer (marked) and sanitizer (DOMPurify) for documents are loaded from jsDelivr, pinned by version and Subresource Integrity hash.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages