Surface Interaction Tools
Relevant source files
The following files were used as context for generating this wiki page:
Surface Interaction Tools define the communication layer between the pi-digby agent and external platforms like Slack or Linear. This system abstracts the platform-specific complexities of message formatting, streaming, file attachments, and reactions into a unified interface, allowing the agent logic to remain agnostic of the delivery mechanism.
The core of this system is the AgentSurface interface, which provides a contract for emitting progress updates, detailed reasoning, and final responses. Implementations like SlackSurface and LinearSurface translate these abstract calls into platform-native operations, such as updating Slack messages in-place or appending activity "thoughts" to Linear issues.
Sources: src/surface/types.ts:1-46, src/surface/slack.ts:26-30, src/surface/linear.ts:13-17
Architecture and Core Interface
The interaction model relies on a provider pattern where the agent interacts with an implementation of the AgentSurface interface. This interface ensures that every agent run follows a predictable lifecycle: starting with a thinking state, proceeding through multiple progress steps (often representing tool executions), and ending with a terminal operation (resolve, reject, or suppress).
The AgentSurface Interface
The interface defines the following critical methods for user interaction:
| Method | Description | Implementation Behavior |
|---|---|---|
emitThinking() | Signals the start of an agent run. | Posts a "Thinking" placeholder. |
emitProgress(text) | Appends tool/step progress (e.g., "→ Reading file"). | Appended to stream or history. |
emitResponse(text) | Sets the final assistant response. | Replaces progress in Slack; appends in Linear. |
emitDetail(text) | Provides supplementary reasoning or debug info. | Threaded in Slack; appends in Linear. |
emitReaction(emoji) | Adds a visual reaction to the triggering message. | Native Slack reaction; thought in Linear. |
emitFile(path) | Shares a file with the user. | File upload or reference link. |
resolve() | Marks the run successful and terminal. | Finalizes UI (adds cost/step footer). |
reject(error) | Marks the run failed and terminal. | Appends error message to the surface. |
suppress() | Silently terminates the run. | Deletes placeholders (Slack) or skips post. |
Sources: src/surface/types.ts:2-46
Component Relationship
The following diagram illustrates how the AgentSurface abstraction is implemented across different platforms:
The diagram shows the class hierarchy where platform-specific surfaces implement the common AgentSurface interface.
Sources: src/surface/types.ts, src/surface/slack.ts, src/surface/linear.ts
Platform Implementations
Slack Surface
The SlackSurface implementation is designed for interactive, in-place updates. It maintains a single message lifecycle for a run, using Slack's updateMessage API to provide a "streaming" feel even for non-streaming tool results.
- Message Lifecycle: Every run begins by posting a
THINKING_PLACEHOLDER("🤔 Thinking"). This is later replaced or appended to byemitProgressandemitResponse. - Serialization: All Slack operations (post, update, delete) are serialized through an
updateChain(a Promise chain) to ensure that message updates happen in the correct order and don't conflict. - Auto-computed Footer: Upon calling
resolve(), the surface automatically appends a footer containing the step count and total execution cost (e.g.,«3 steps · $0.05»). - Formatting: It automatically converts standard Markdown (
**bold**,*italic*) to Slack-specificmrkdwn(*bold*,_italic_).
Sources: src/surface/slack.ts:31-75, src/surface/slack.ts:139-174, src/surface/types.ts:48
Linear Surface
The LinearSurface behaves differently due to the append-only nature of Linear issue activities.
- Buffering: Unlike Slack,
emitResponsebuffers text locally. The final response is only posted onceresolve()is called. - Persistent Progress:
emitProgresscalls (tool starts, retries) are emitted as persistent "thoughts" (issue activities) so that users can scroll through the agent's history. - Graceful Fallbacks: Features not natively supported in Linear activities, like emoji reactions or direct file uploads, are translated into activity text entries.
Sources: src/surface/linear.ts:13-77
Event Handling and Data Flow
The EventHandler acts as the bridge between the core AgentSession (where LLM logic and tool execution occur) and the AgentSurface. It listens for session events and triggers the corresponding surface methods.
The sequence diagram details the flow of execution events from the agent session through the handler to the platform-specific surface.
Sources: src/agent/events.ts:107-160, src/agent/setup.ts:250-255
Tool Integration
In src/agent/setup.ts, specific tool functionalies are wired directly into the surface during the run initialization:
- Attach Tool: The
uploadFnin the tool context is mapped toctx.emitFile(filePath, title). - React Tool: The
reactionFnin the tool context is mapped toctx.emitReaction(emoji, event.ts).
Sources: src/agent/setup.ts:240-247
Interaction Constraints and Formatting
Slack Formatting Logic
The system handles the discrepancy between standard Markdown used by the LLM and Slack's mrkdwn format.
function mdToMrkdwn(text: string): string {
return text
.replace(/(?<!\*)\*([^*]+)\*(?!\*)/g, "_$1_") // italic first, skip **
.replace(/\*\*([^*]+)\*\*/g, "*$1*"); // then collapse ** to *
}
Sources: src/surface/slack.ts:11-18
System Prompt Guidance
The system prompt (built in src/agent/prompt.ts) explicitly instructs the agent on how to use these tools based on the active surface.
- For Slack: Instructed to use
mrkdwn, mention users via<@username>, and use thereacttool for silent acknowledgments. - For Linear: Instructed to use standard Markdown and informed that responses will be posted as agent activities.
Sources: src/agent/prompt.ts:40-60, src/agent/prompt.ts:180-185
Summary of Terminal Operations
The lifecycle of a surface interaction must end with one of three terminal states to ensure resource cleanup and UI consistency.
| Operation | Purpose | Final State |
|---|---|---|
| Resolve | Successful completion. | Updates message with final text and cost footer. |
| Reject | Error encountered. | Appends error icon (⚠️) and description to the UI. |
| Suppress | LLM returned [SILENT]. | Deletes the placeholder message and all thread details. |
Sources: src/surface/slack.ts:139-174, src/surface/types.ts:31-37