{"id":50485141,"url":"https://github.com/vinodhalaharvi/loom","last_synced_at":"2026-06-01T21:30:28.344Z","repository":{"id":360214577,"uuid":"1245129944","full_name":"vinodhalaharvi/loom","owner":"vinodhalaharvi","description":"Slack front-end for Sibyl — weaves Slack activity into durable agent execution","archived":false,"fork":false,"pushed_at":"2026-05-25T13:58:29.000Z","size":90,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-25T14:31:31.183Z","etag":null,"topics":["agents","ai-agents","golang","sibyl","slack","slack-bot","socket-mode","temporal"],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/vinodhalaharvi.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-21T00:08:18.000Z","updated_at":"2026-05-25T13:09:52.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/vinodhalaharvi/loom","commit_stats":null,"previous_names":["vinodhalaharvi/loom"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/vinodhalaharvi/loom","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vinodhalaharvi%2Floom","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vinodhalaharvi%2Floom/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vinodhalaharvi%2Floom/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vinodhalaharvi%2Floom/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vinodhalaharvi","download_url":"https://codeload.github.com/vinodhalaharvi/loom/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vinodhalaharvi%2Floom/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33795112,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-01T02:00:06.963Z","response_time":115,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["agents","ai-agents","golang","sibyl","slack","slack-bot","socket-mode","temporal"],"created_at":"2026-06-01T21:30:27.324Z","updated_at":"2026-06-01T21:30:28.337Z","avatar_url":"https://github.com/vinodhalaharvi.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# loom\n\nA Slack front end for [Sibyl](https://github.com/vinodhalaharvi/sibyl), the\nagent execution engine. loom listens to Slack over a Socket Mode WebSocket,\nturns each event into a typed value, runs a handler, and posts the result\nback. It weaves the threads of Slack activity into durable agent execution.\n\n\u003e **Status:** PR-3 — the full loop. A Slack message is translated to\n\u003e AgentScript by an LLM, compiled to a Sibyl execution Plan (the compiler\n\u003e validates it — invalid commands are rejected, never executed), and run\n\u003e as a durable `PlanWorkflow`; the result is posted back to the thread.\n\u003e Requires a Temporal cluster, a Sibyl worker, and `ANTHROPIC_API_KEY`.\n\n## The idea\n\nSibyl is the workhorse: durable, Temporal-backed agent execution with\nper-agent OAuth. It is unchanged by loom. loom is one *front end* to Sibyl —\na peer of the `agentscript` DSL — that uses Slack as the interface instead of\na command line or a custom language. Slack already solves the hard\ninterface problems: input and output, file upload/download, identity,\nchannels as scope boundaries, threads as sessions, mobile, search, history.\n\nThe architectural rule (inherited from Sibyl's design): **loom is\npresentation and routing only.** Behavior, state, durability, and credential\nhandling live in Sibyl. loom translates Slack events into Sibyl invocations\nand Sibyl results into Slack replies. It never holds a vendor credential.\n\n## How it works\n\n```mermaid\nflowchart TD\n    subgraph slack[\"Slack\"]\n        evt[\"message · @mention · /command\"]\n        reply[\"reply: result · 'working…' · rejection\"]\n    end\n\n    subgraph loom[\"loom — Arrow[Event, Reply]\"]\n        ws[\"Socket Mode WebSocket\u003cbr/\u003e(outbound; no public URL)\"]\n        ack[\"ack within 3s\"]\n        tr[\"translate → Event\"]\n        llm[\"Translator (LLM)\u003cbr/\u003eprose → AgentScript DSL\"]\n        rn[\"render → Slack API\"]\n    end\n\n    subgraph as[\"AgentScript (pkg/script)\"]\n        compile[\"Compile: Parse→Resolve→Lower\u003cbr/\u003e→Finalize→Validate\"]\n        plan[\"validated Plan\u003cbr/\u003e(rejects unknown/bad commands)\"]\n    end\n\n    subgraph sibyl[\"Sibyl\"]\n        submit[\"Submit → PlanWorkflow\"]\n        work[\"durable execution\u003cbr/\u003e(named activities)\"]\n    end\n\n    evt --\u003e ws --\u003e ack --\u003e tr --\u003e llm --\u003e compile --\u003e plan --\u003e submit --\u003e work\n    compile -.-\u003e|invalid DSL| reply\n    work -.-\u003e|PlanResult| reply\n    rn --\u003e reply\n```\n\nThe compiler is the safety net: an LLM that emits an unknown command or\nbad arguments fails at `Compile`, and loom replies with a friendly\n\"I couldn't turn that into a valid command\" — nothing wrong executes.\n\nTwo directions touch Slack, and they live in different repos on purpose:\n\n- **Ingress** (loom): the Socket Mode listener that receives events. Sibyl's\n  `channels/slack` deliberately does *not* listen — it posts and polls — so\n  there is no overlap.\n- **Egress / HITL** (Sibyl's `channels/slack`): when a *running workflow*\n  needs to ask a human something, it posts and waits via Sibyl's existing\n  Slack channel. loom doesn't mediate that path.\n\n## Design decisions\n\n- **Single-turn, no streams (YAGNI).** Each Slack event is one\n  `Arrow[Event, Reply]` invocation. Multi-message-over-time behavior (a\n  workflow posting progress) is reconstructed from *correlation* (thread ↔\n  workflow) plus Sibyl's durability — not from a streaming arrow. A streaming\n  arrow would duplicate the durability that already lives in Temporal. We add\n  one only if a genuine live-stream feature (e.g. token-by-token message\n  edits with nothing durable behind them) ever demands it.\n- **The 3-second ack.** Slack redelivers any event not acknowledged within\n  ~3s. A workflow can take minutes, so the loop acks *immediately* and\n  dispatches handling to a bounded worker pool. Results come back\n  asynchronously via Sibyl's egress, not as the synchronous response to the\n  event. This is exactly why the single-turn model fits: the handler's job\n  per event is bounded.\n- **Reply is data, not action.** Handlers return a `Reply` value; a single\n  `render` step at the edge turns it into Slack API calls. Everything before\n  `render` is pure and testable.\n- **Slack nouns are context, not arrows.** Channel, user, thread become\n  `Context` that rides alongside the data (the analog of Sibyl's\n  `AgentContext`). Only `Event → Reply` is composed.\n\n## Running\n\nloom needs two tokens and Socket Mode enabled on your Slack app:\n\n```bash\nexport SLACK_BOT_TOKEN=\"xoxb-...\"   # Web API: post, react, user info\nexport SLACK_APP_TOKEN=\"xapp-...\"   # Socket Mode: connections:write\nexport ANTHROPIC_API_KEY=\"...\"      # LLM for prose → AgentScript DSL\n\n# end-to-end execution also needs a Temporal cluster + Sibyl worker:\n#   temporal server start-dev\n#   go run ./cmd/worker   (in the sibyl repo)\ngo run ./cmd/loom\n```\n\nSlack app setup:\n\n1. Enable **Socket Mode**.\n2. Create an **App-Level Token** with `connections:write` → `SLACK_APP_TOKEN`.\n3. **OAuth \u0026 Permissions** bot scopes: `app_mentions:read`, `chat:write`,\n   `reactions:write`, `channels:history`, `im:history` (and `commands` if you\n   want slash commands).\n4. **Event Subscriptions** → bot events: `app_mention`, `message.channels`,\n   `message.im`.\n5. Install to the workspace → `SLACK_BOT_TOKEN`.\n\nThen `@mention` the bot in a channel it's in. PR-1 reacts 👀 and echoes your\ntext back in-thread.\n\n## Layout\n\n```\nloom/\n├── cmd/loom/main.go    # entry point: tokens + LLM + Temporal, wires the handler\n├── types.go            # Event, Context, Reply, Handler — the mapping\n├── translate.go        # Slack event → loom.Event (the ingress half)\n├── listener.go         # Socket Mode connection, ack, bounded dispatch\n├── render.go           # Reply → Slack API calls (the one effectful edge)\n├── correlation.go      # ThreadID ↔ WorkflowID table (B1)\n├── sibyl.go            # PlanClient seam: Start / Await + async runner\n├── sibyl_handler.go    # ScriptHandler: prose → scriptmem.Execute → memory result | temporal plan\n├── handler.go          # EchoHandler (PR-1; kept for reference/testing)\n└── *_test.go           # handler (incl. safety-net) / correlation tests\n```\n\n## Roadmap\n\n- **PR-1:** Socket Mode listener, typed event mapping, echo handler.\n- **PR-2:** depend on Sibyl; B1 correlation — start a workflow, record\n  thread↔workflow, background-await, post the result.\n- **PR-3 (this):** the full loop. Depend on AgentScript's `pkg/script`;\n  an LLM translates the Slack message to AgentScript DSL; `script.Compile`\n  validates it (unknown/malformed commands are rejected, never executed);\n  `script.Submit` runs it as a durable Sibyl `PlanWorkflow`; the result is\n  posted back to the thread. The composition lives in the DSL — the LLM is\n  its author, the compiler its safety net.\n- **Later:**\n  - More builtins beyond `echo` (each = one registry entry in AgentScript\n    + one registered Sibyl activity).\n  - **Auth button.** A workflow's missing-credential failure → a\n    \"🔑 Authorize\" reply. Needs a small Sibyl-side change (typed\n    `ApplicationError`) paired with the loom translation; a focused PR when\n    loom drives an OAuth-using activity.\n  - Multi-turn thread continuity (route a follow-up reply into the existing\n    workflow via the correlation table).\n  - Channel-scoped command availability (\"roles via channels\").\n  - File handling; Slack-ID → canonical identity mapping.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvinodhalaharvi%2Floom","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvinodhalaharvi%2Floom","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvinodhalaharvi%2Floom/lists"}