Simple Workspace Agent Algebra
Recently, I worked on an AI agent for Slack. You can mention it in a channel, ask a follow-up in a thread, or talk to it in a DM. Its job is to keep track of what is happening across the organization and join a conversation when it has something useful to add.
The design starts with one choice: use the channel to decide what the agent can see and do. Most of the work already happens there. A channel is a room with a record of its conversations. The organization, by using Slack, has already decided who can enter and see the context.
One of the tradeoffs is that someone might belong to both #support and private #pricing. That doesn't mean everything they know belongs in a #support reply. Following the channel's permissions means the agent sometimes has less information than the person asking.
I was inspired by Sandy's Algebra-Driven Design. Basically, the idea is to describe a few operations and the laws they obey, then build and test against those laws.
Now, let's decompose Slack.
Each request gives the agent two things: context and tools.
For ordinary internal channels, context comes from the workspace's public channels plus the channel where you ask. DMs, guests, and shared channels need separate rules.
In #support, the agent searches public messages. In private #pricing, it can also search #pricing. Threads follow the same rule as their channel.
A tool must be available in the workspace and enabled in the channel:
The workspace approves the tool catalog, including tools from connected MCP servers. Each channel chooses from that catalog. For example:
Ask why a customer got a discount in #support, and the agent has public messages and customer lookup to work with. Ask in #pricing, and it also has the private discussion and billing lookup. This stays true even when the same person asks both questions.
Why typed algebra
I want a solid foundation for automated software development. The idea is to encode the core rules in types, so combinations that break those rules are impossible to represent and the compiler can reject them. AI can then write more of the code without having to remember every rule itself.
For example, the Slack agent only accepts a request whose context and tools have been checked together:
An AI coding agent tries to give #support the full tool catalog, including lookupBilling. It cannot pass that catalog directly to run; the types do not match. It has to prepare the request, which applies the channel's permissions and leaves billing out.
The permission check happens at runtime. With construction restricted to these checks, the compiler rejects unchecked requests, including in AI-generated code.
I use the same approach throughout: composing tools and capabilities, applying channel preferences, and assembling context. I model permission decisions as pure, referentially transparent functions: the same request and policy snapshot produce the same result, with no side effects. Reading current permissions and executing tools happen separately. This lets me test and replay a decision without running the agent or calling external services.