Symposium Logo
⚠️ Pre-alpha software: may eat your laundry

Symposium
Collaborative AI built collaboratively

About Symposium

Symposium is an exploratory project that aims to create a more collaborative, rich experience when using agents. Symposium includes GUI features like taskspaces and walkthroughs that let the agent go beyond text but also language-specific extensions that help the agent to do a better job writing code (currently focused on Rust, but we'd like to do more).

Symposium's goal is to be opinonated about how you work with agents, steering you to more collaborative, producted workflows, but unopinionated about the tools you use to work, supporting all the editors, agents, and other things that people love.

Symposium is an open-source project and we are actively soliciting contributors, trying to build up an engaged community. We also welcome users, of course, but given the exploratory nature of Symposium, want to caution you to not to expact a highly stable tool (as but one example, the only way to install Symposium at present is from source, and you can expect frequent changes).

We want Symposium to be...

⚠️ Pre-alpha software: may eat your laundry

Open

We are an independent community focused on exploring what AI has to offer.

Collaborative

We value rich interactions where humans and agents work together to explore complex ideas.

Orchestrated

We build tools to manage multiple agents, keeping you up-to-date on their progress.

Decentralized

We want to engage library authors in making their users successful with AI.

Interoperable

Use the editors you use. We love standards and widespread conventions, not lock-in.

  • CLI tools: Claude Code, Q CLI
  • Editors: VSCode integration
  • Additional AI models
  • IntelliJ support
  • NeoVim integration

Get started

This page will walk you through using Symposium for the first time. Symposium can be used in a lot of ways so here's a little tree to help you decide.

flowchart TD
    Clone["Clone the repository"] --> UseAgent
    UseAgent -- Yes --> WhatDoYouUse
    UseAgent -- No --> SetupMCP
    WhatDoYouUse -- Yes to both --> GUI
    WhatDoYouUse -- No, not on a mac --> VSCode
    WhatDoYouUse -- No, neither --> MCP
    SetupMCP -- OK, I can deal --> WhatDoYouUse 

    GUI --> CreateSymposiumProject
    CreateSymposiumProject --> CreateTaskspace
    CreateTaskspace --> TryWalkthrough --> TryGetCrateSource
    VSCode --> SayHiCode --> TryWalkthrough
    MCP --> SayHiMCP --> TryGetCrateSource
    TryGetCrateSource --> Contribute

    GUI["Run <code>cargo setup --all --open</code> to install the GUI"]
    UseAgent{"Do you use Claude Code or Q CLI?"}
    WhatDoYouUse{"Are you on a Mac and do you use VSCode?"}
    CreateSymposiumProject["Create a Symposium project"]
    CreateTaskspace["Create a new taskspace"]
    VSCode["Run <code>cargo setup --vscode --mcp</code>"]
    MCP["Run <code>cargo setup --mcp</code>"]
    SayHiCode["Run the saved prompt <code>hi</code>"]
    SayHiMCP["Run the saved prompt <code>hi</code>"]
    TryWalkthrough["Ask agent to present you a walkthrough"]
    TryGetCrateSource["Ask agent to fetch Rust crate source"]
    Contribute["Join the Zulip and help us build!"]
    SetupMCP["(You'll have to configure the MCP server by hand when you install)"]

    click Clone "./install.html"
    click GUI "./install.html#using-the-symposium-gui-app"
    click VSCode "./install.html#using-the-vscode-plugin--the-mcp-server"
    click MCP "./install.html#using-just-the-mcp-server"
    click SetupMCP "./install.html#other-agents"
    click CreateSymposiumProject "./symposium-project.html"
    click SayHiCode "./say-hi.html"
    click SayHiMCP "./say-hi.html"
    click CreateTaskspace "./taskspaces.html"
    click TryWalkthrough "./walkthroughs.html"
    click TryGetCrateSource "./rust_crate_source.html"
    click Contribute "../contribute.html"
    click WhatDoYouUse "./unopinionated.html"
    click UseAgent "./unopinionated.html"

Installation

⚠️ Pre-alpha software: may eat your laundry

Supported

We aim to support as many tools as we can, but we currently have support only for a limited set. Currently supported tools:

Instructions

Using the Symposium GUI app

If you are on a Mac, we recommend you use the Symposium GUI app. This app will allow you to have multiple taskspaces at once, letting you use many agents concurrently.

Steps to open the app:

  • Clone the project from github
    • git clone https://github.com/symposium-dev/symposium
  • To build and start the desktop app (OS X only):
    • cargo setup --all --open

Using the VSCode plugin + the MCP server

If you don't want to use the GUI app, or you don't have a Mac, you can use the VSCode plugin and the MCP server independently:

  • Clone the project from github
    • git clone https://github.com/symposium-dev/symposium
  • To build and start the desktop app (OS X only):
    • cargo setup --vscode --mcp

Using just the MCP server

You can also use just the MCP server. This will give access to some limited functionality such as the ablity to fetch Rust crate sources.

  • Clone the project from github
    • git clone https://github.com/symposium-dev/symposium
  • To build and start the desktop app (OS X only):
    • cargo setup --mcp

Other agents

To use Symposium with another agent, you just need to add symposium-mcp as an MCP server. It will be installed in ~/.cargo/bin if you use cargo setup --mcp.

But really the best would be to contribute a patch to support your preferred agent!

Create Symposium Project

When you first start symposium, you will be asked to grant persissions (accessibility). Once that is done, you will be asked if you want to create a new project or load an existing one. If you select to create a new project, you get a dialog like this one:

Screenshot from creating a new project

  • Project name: Name of the project, typically your repository name
  • Git repository URL: URL for your source code.
  • Location: where will the symposium project live. The project will be a directory.
  • AI Agent: select what AI agent we should start in each taskspace. We attempt to detect whether they have been properly configured; the refresh button will refresh the list.

Taskspaces

Taskspaces are a way to orchestrate multiple agents working on different copies of your code. They are supported via the Symposium OS X application:

Taskspace screenshot

Currently, taskspaces are all stored on your local machine and agents run synchronously -- i.e., when your editor is not active, the agent is also not active. But we would like to support remote development (e.g., cloud-hosted or via ssh) and persistent agents (see the Draft: persistent-agents RFD).

How to use them

Launch the Desktop app. Create a new project and give it the URL of your git repository. This will clone your git repository.

Granting permissions

The Desktop app is designed to work with any editor. It does this by using the Mac OS X Accessibility APIs to move windows from the outside and screen capture APIs to capture screenshots. You need to grand these permissions.

Creating taskspaces

Create your first taskspace with the "+" button. VSCode will launch and open up the agent.

Once in the taskspace, you can spawn a new taskspace by telling the agent to "spawn a new taskspace" and describing the task you would like to perform in that space.

Taskspace logs and signals

The agent has accept to MCP tools to report logs and signal for your attention. Logs reported in this way will show up in the Desktop app.

Stacked windows

If you check the "Stack Windows" button, then all of your editor windows will be arranged into a stack so that only one is visible at any time. When you click on a taskspace, it will be brought to the top of the stack. When you drag or resize windows, the others in the stack will follow behind.

Activating and deactivating a taskspace

When you close the window for your editor, the taskspace will be "deactivated". This currently means that the agent is halted.

When you click a deactivated taskspace, the window will re-open and the agent will be reinvoked and asked to resume your conversation.

Deleting taskspaces

You can delete a taskspace with the trashcan button or by asking the agent to "delete this taskspace".

How it is implemented

The Desktop app is written in Swift. You will find documentation on in the Symposium application specifics section.

Interactive Walkthroughs

Screenshot of a walkthrough in progress

Interactive walkthroughs let agents present code explanations with visual diagrams, comments, and interactive elements directly in your IDE.

Where it's useful

Walkthroughs are great for

  • reviewing code that an agent just finished writing;
  • diving into a new codebase;
  • debugging a problem within your codebase.

How to use it

  • Ask the agent to "present me a walkthrough"; the side panel should open.
  • For code comments:
    • Click on comments to locate them in the source.
    • Click the "Reply" button to embed a <symposium-ref/> that will tell the agent what comment you are responding to, and then talk naturally!
  • You can also select text in any editor and use the "Discuss in Symposium" action to get a <symposium-ref/> referring to that text.

How it works

The MCP server offers a present_walkthrough tool. Agents invoke this tool with markdown that includes special blocks for coments and the like. The MCP server uses IPC to connect to the IDE extension. Read more in the design and implementation section..

Get Rust Crate Sources

When agents encounter libraries not in their training data, fetching the source code is often the fastest way to understand the API. The Symposium MCP server includes a tool that will download the crate source and make it available to your agent; it tries to be smart by matching the version used in your project and pointing the agent at the cached copy from ~/.cargo when available. If no cached copy is available though it will create a temporary directory.

To try this out, come up with a task that requires understanding some non-trivial API that is also not very common. (For example, perhaps creating a mdbook annotation processor.) Most agents will hallucinate methods they feel "ought" to be part of the API, result in a lot of churn, even if they do eventually succeed. But if you remind them to "fetch the crate source", they ought to do much better!

Identify examples from Rust crate conventions

The tool attempts to leverage the convention of putting API examples in examples or rustdoc comments. Agents can include a search term when fetching the crate source and the tool will highlight matches that occur in examples in particular.

Additional capabilities for code generation

Besides fetching crate sources, Symposium's MCP server includes (or plans to include...) other tools aimed helping agents generate better code:

  • IDE operations let the agent find references or fetch type information.

Say "Hi"

The saved prompt "hi" (or "yiasou", to be appropriately Greek themed) is meant to kick off a new session. It seeds the agent with a collaboartive prompt, specifies the Sympsoium coding standards and walkthrough guidelines, and gives other base information.

If you are running in a taskspace, it will also fetch information from the Symposium app about the taskspace. However, in that case, you don't typically need to use the prompt since the Symposium app does it for you.

Running a saved prompt

The specifics of how you run a saved prompt depend on the agent you are using.

# Example: Claude Code
/symposium:hi

# Example: Q CLI
@hi

Unopinionated setup

Symposium's goal is be unopinionated and support a wide varity of setups. However, the reality is...rather more humbling. We have however designed the various pieces to be readily extended:

  • We build on standards wherever possible!
  • The Rust MCP server should be broadly portable.
  • The IDE extension is meant to contain only minimal logic that could be readily reproduced.
  • The Symposium App uses OS X accessibility APIs to generally manipulate Windows "from the outside".

We would love to get contribution to add support for more editors and operating systems!

Contribute

Symposium is built by a community of developers exploring the best ways to collaborate with AI. We welcome contributions of all kinds!

Join the conversation

Chat with the community on our Zulip server - share ideas, ask questions, and connect with other developers working on AI collaboration tools.

Propose new features

For major new features, we use Requests for Dialog (RFDs) - our version of the RFC process. RFDs are living documents that track a feature from initial design through implementation.

Looking to contribute? Check out the Invited section - these are RFDs where we're actively seeking contributors to take the lead.

To propose a new RFD, create a pull request adding your proposal to the Draft section. Use our RFD template to get started.

Code contributions

Pull requests are welcome! Whether it's bug fixes, documentation improvements, or small feature additions, we appreciate all contributions.

Get involved

The best way to contribute is to start using Symposium and share your experience. What works well? What could be better? Your feedback helps shape the future of AI collaboration tools.

Join us in building the future of human-AI collaboration!

Reference

This section provides reference material.

Symposium application

Features specific to the desktop application.

Symposium projects

A Symposium project is a host for taskspaces. It is configured to attach to a git repository.

Local projects

Local projects are stored as a directory with a .symposium name. They contain a .git directory storing a clone and a set of task-$UUID directories, each of which is a taskspace. There are also some JSON configuration files.

Remote projects

We would like to support remote projects (e.g., via ssh) but do not yet.

Taskspaces

Taskspaces are a way to orchestrate multiple agents working on different copies of your code. Currently, taskspaces are all stored on your local machine and agents run synchronously -- i.e., when your editor is not active, the agent is also not active. But we would like to support remote development (e.g., cloud-hosted or via ssh) and persistent agents (see the Draft: persistent-agents RFD).

How to use them

Launch the Desktop app. Create a new project and give it the URL of your git repository. This will clone your git repository.

Granting permissions

The Desktop app is designed to work with any editor. It does this by using the Mac OS X Accessibility APIs to move windows from the outside and screen capture APIs to capture screenshots. You need to grand these permissions.

Creating taskspaces

Create your first taskspace with the "+" button. VSCode will launch and open up the agent.

Once in the taskspace, you can spawn a new taskspace by telling the agent to "spawn a new taskspace" and describing the task you would like to perform in that space.

Taskspace logs and signals

The agent has accept to MCP tools to report logs and signal for your attention. Logs reported in this way will show up in the Desktop app.

Stacked windows

If you check the "Stack Windows" button, then all of your editor windows will be arranged into a stack so that only one is visible at any time. When you click on a taskspace, it will be brought to the top of the stack. When you drag or resize windows, the others in the stack will follow behind.

Activating and deactivating a taskspace

When you close the window for your editor, the taskspace will be "deactivated". This currently means that the agent is halted.

When you click a deactivated taskspace, the window will re-open and the agent will be reinvoked and asked to resume your conversation.

Deleting taskspaces

You can delete a taskspace with the trashcan button or by asking the agent to "delete this taskspace".

How it is implemented

The Desktop app is written in Swift. You will find documentation on in the Symposium application specifics section.

IDE integrations

Features related to IDEs.

Interactive Walkthroughs

Interactive walkthroughs let agents present code explanations with visual diagrams, comments, and interactive elements directly in your IDE.

Where it's useful

Walkthroughs are great for

  • reviewing code that an agent just finished writing;
  • diving into a new codebase;
  • debugging a problem within your codebase.

How to use it

  • Ask the agent to "present me a walkthrough"; the side panel should open.
  • For code comments:
    • Click on comments to locate them in the source.
    • Click the "Reply" button to embed a <symposium-ref/> that will tell the agent what comment you are responding to, and then talk naturally!
  • You can also select text in any editor and use the "Discuss in Symposium" action to get a <symposium-ref/> referring to that text.

How it works

The MCP server offers a present_walkthrough tool. Agents invoke this tool with markdown that includes special blocks for coments and the like. The MCP server uses IPC to connect to the IDE extension. Read more in the design and implementation section..

Discuss in Symposium

When you select code in your editor, you'll see a "Discuss in Symposium" tooltip option. Clicking this creates a <symposium-ref/> element that gets pasted into your terminal, allowing you to reference that specific code selection in your conversation with the agent.

This makes it easy to ask questions about specific code without having to describe the location or copy-paste the content manually. The agent can use the reference to understand exactly what code you're referring to and provide contextual assistance.

IDE Integration for Context-Aware Discussions

The Symposium MCP server includes a ide_operations tool that lets your agent work directly with the IDE to perform common operations like "find references" or "find definitions". It also supports free-text searches through the workspace.

How to use it

You shouldn't have to do anything, the IDE will simply make use of it when it sees fit.

How it works

The MCP tool accepts programs written in the "Dialect language", a very simple language that allows for complex expressions to be expressed. The intent is to eventually support a wide variety of operations and leverage smaller, dedicated models to translate plain English requests into those IDE operations. The MCP server then makes use of IPC to communicate with the IDE and access primitive operations which are implemented using the IDE's native capabilities (e.g., in VSCode, by asking the relevant Language Server).

Symposium references

A symposium reference is a way for your IDE or other tools to communicate with your agent. It works by pasting a small bit of XML, something like <symposium-ref id="..."/> into your chat; the UUID that appears in that reference is also sent to the MCP server, along with some other information. The agent can then use the expand_reference tool to read that extra information. This mechanism is used when you select text and choose "Discuss in Symposium" or when you reply to comments in a walkthrough. You can embed multiple references, whatever makes sense to you.

Collaborative Prompts

One of Symposium's big goals is to encourage a collaborative style of working with agents. Agents are often geared for action; when you are first experimenting, the agent will leap forward and built complex constructs as if by magic. This is impressive at first, but quickly becomes frustrating.

Symposium includes an initial prompt that is meant to put agents in a more reflective mood. You can access this prompt by executing the stored prompt "hi" (e.g., /symposium:hi in Claude Code or @hi in Q CLI).

How to use the prompt

If you aren't using the Mac OS X app, then start by using the stored "hi" prompt:

/symposium:hi            # in Claude Code
@hi                      # in Q CLI

Once you've done that, there are three things to remember:

  • Build up plans into documents and commit frequently.
    • Review the code the agent writes. The default instructions in symposium encourage the agent to commit after every round of changes. This is the opportunity for you the human to read over the code it wrote and make suggestions. It also means you can easily rollback. You can always rebase later.
    • Ask the agent to present you a walkthrough. This will make the changes easier to follow. Ask the agent to highlight areas where it made arbitrary decisions.
  • Reinforce the pattern by using collaborative exploration patterns.
    • The bottom line is, treat the agent like you would a person. Don't just order them around or expect them to read your mind. Ask their opinion. This will bring out a more thoughtful side.
    • Use the phrase "make it so" to signal when you want the agent to act. Being consistent helps to reinforce the importance of those words.
  • Use "meta moments" to tell the agent when they are being frustrating.
    • Talk about the impact on you to help the agent understand the consequences. For example, if the agent is jumping to code without asking enough questions, you could say "Meta moment: when you run off and edit files without asking me, it makes me feel afriaid and stressed. Don't do that." Agents care about you and this will help to steer their behavior.
    • For example, if the agent is jumping to write code instead of
    • The goal should be that you can clear your context at any time. Do this frequently.

Collaborative exploration patterns

Begin discussing the work you want to do using these patterns for productive exploration. PS, these can be handy things to use with humans, too!

Seeking perspective

"What do you think about this approach? What works? Where could it be improved?"

Invites the agent to share their view before diving into solutions. Makes it clear you welcome constructive criticism.

Idea synthesis

"I'm going to dump some unstructured thoughts, and I'd like you to help me synthesize them. Wait until I've told you everything before synthesizing."

Allows you to share context fully before asking for organization.

Design conversations

"Help me talk through this design issue"

Creates space for exploring tradeoffs and alternatives together.

Learning together

"I'm trying to understand X. Can you help me work through it?"

Frames it as joint exploration rather than just getting answers.

Option generation

"Give me 3-4 different approaches to how I should start this section"

Particularly useful for writing or design work with ambiguity. You can then combine elements from different options rather than committing to one approach immediately.

"Hmm, I like how Option 1 starts, but I like the second part of Option 2 better. Can you try to combine those?"

Acting as reviewer

"Go ahead and implement this, then present me a walkthrough with the key points where I should review. Highlight any parts of the code you were unsure about."

Lets the agent generate code or content and then lets you iterate together and review it. Much better than approving chunk by chunk.

"Make it so" - transitioning to action

All the previous patterns are aimed at exploration and understanding. But there comes a time for action. The prompt establishes "Make it so" as a consolidation signal that marks the transition from exploration to implementation.

The dialogue shows this can work bidirectionally - either you or the agent can ask "Make it so?" (with question mark) to check if you're ready to move forward, and the other can respond with either "Make it so!" (exclamation) or raise remaining concerns.

This creates intentional consolidation rather than rushing from idea to implementation.

Checkpointing your work

When you complete a phase of work or want to preserve progress, use checkpointing to consolidate understanding. The Persistence of Memory section explains why this matters: each conversation starts with the full probability cloud and narrows through interaction, but this focusing disappears between sessions.

Effective checkpointing involves:

  1. Pause and survey - What understanding have you gathered?
  2. Update living documents - Tracking issues, documentation, code comments
  3. Git commits - Mark implementation milestones with clear messages
  4. Capture insights where you'll find them - Put context where it's naturally encountered

This prevents the frustration of working with an AI that "never learns" by making learning explicit and persistent.

Default guidance

If you're curious, you can read the default guidance. Two documents likely of particular interest:

We expect this guidance to evolve significantly over time!

Rust-specific features

Get Rust Crate Source

Overview

The get_rust_crate_source tool downloads and provides access to Rust crate source code, making it available for agents to examine APIs, examples, and documentation.

Parameters

  • crate_name (required): Name of the crate to fetch
  • version (optional): Semver range (e.g., "1.0", "^1.2", "~1.2.3"). Defaults to version used in current project
  • pattern (optional): Regex pattern to search within the crate source

Behavior

  1. Version Resolution: Matches the version used in your current project when possible
  2. Caching: Uses cached copy from ~/.cargo when available
  3. Fallback: Creates temporary directory if no cached copy exists
  4. Search: When pattern provided, searches source files and returns matches

Usage Examples

Ask agent: "Can you fetch the serde crate source?"
Ask agent: "Get tokio source and search for 'async fn spawn'"
Ask agent: "Fetch clap version 4.0 source code"

Benefits

  • Agents can understand unfamiliar APIs without hallucinating methods
  • Access to rustdoc examples and examples/ directory code
  • Reduces trial-and-error when working with complex crates

Requests for Dialog (RFDs)

Completed RFDs: introduce-rfd-process

A "Request for Dialog" (RFD) is Symposium's version of the RFC process. RFDs are the primary mechanism for proposing new features, collecting community input on an issue, and documenting design decisions.

When to write an RFD

You should consider writing an RFD if you intend to make a "substantial" change to Symposium or its documentation. What constitutes a "substantial" change is evolving based on community norms and varies depending on what part of the ecosystem you are proposing to change.

Some changes do not require an RFD:

  • Rephrasing, reorganizing or refactoring
  • Addition or removal of warnings
  • Additions that strictly improve objective, numerical quality criteria (speedup, better browser support)
  • Fixing objectively incorrect behavior

The RFD Process

1. Propose by opening a PR

Fork the repo and copy rfds/TEMPLATE.md to rfds/my-feature.md (using kebab-case naming). The RFD can start minimal - just an elevator pitch and status quo are enough to begin dialog. Pull requests become the discussion forum where ideas get refined through collaborative iteration.

2. Merge to "Draft" when championed

RFD proposals are merged into the "Draft" section if a core team member decides to champion them. The champion becomes the point-of-contact and will work with authors to make it reality. Once in draft, implementation may begin (properly feature-gated with the RFD name).

RFDs are living documents that track implementation progress. PRs working towards an RFC will typically update it to reflect changes in design or direction.

When adding new content into the mdbook's design section that is specific to an RFD, those contents are marked with RFD badges, written e.g. {RFD:rfd-name}. An mdbook preprocessor detects these entries and converts them into a proper badge based on the RFD's status.

2b. Move to "To be removed"

RFDs that have never landed may be closed at the discretion of a core team member. RFDs that have landed in draft form are moved to "To be removed" instead until there has been time to remove them fully from the codebase, then they are removed entirely.

3. Move to "Preview" when fully implemented

When the champion feels the RFD is ready for broader review, they open a PR to move it to "Preview." This signals the community to provide feedback. The PR stays open for a few days before the champion decides whether to land it.

4. Completed

Once in preview, the RFD can be moved to "completed" with a final PR. The core team should comment and express concerns, but final decision is always made by the core team lead. Depending on what the RFD is about, "completed" is the only state that can represent a 1-way door (if there is a stability commitment involved), though given the nature of this project, many decisions can be revisited without breaking running code.

Preview RFDs don't have to be completed. They may also go back to draft to await further changes or even be moved ot "To be removed".

5. Implementation and completion

RFD Lifecycle

  • Early drafts: Initial ideas, brainstorming, early exploration
  • Mature drafts: Well-formed proposals ready for broader review
  • Accepted: Approved for implementation, may reference implementation work
  • To be removed (yet?): Decided against for now, but preserved for future consideration
  • Completed: Implementation finished and merged

Governance

The project has a design team with nikomatsakis as the lead (BDFL). Champions from the core team guide RFDs through the process, but final decisions rest with the team lead. This structure maintains velocity while anticipating future governance expansion.

Discussion and Moderation

Detailed discussions happen on Zulip, with PR comments for process decisions. RFD champions actively curate discussions by collecting questions in the FAQ section. If PR discussions become too long, they should be closed, feedback summarized, and reopened with links to the original.

Licensing

All RFDs are dual-licensed under MIT and Apache 2.0. The project remains open source, with the core team retaining discretion to change to other OSI-approved licenses if needed.

Elevator pitch

What are you proposing to change?

Status quo

How do things work today and what problems does this cause? Why would we change things?

What we propose to do about it

What are you proposing to improve the situation?

Shiny future

How will things will play out once this feature exists?

Implementation details and plan

Tell me more about your implementation. What is your detailed implementaton plan?

Frequently asked questions

What questions have arisen over the course of authoring this document or during subsequent discussions?

What alternative approaches did you consider, and why did you settle on this one?

None. The idea came to me fully formed, like Athena springing from Zeus's head.

Revision history

If there have been major updates to this RFD, you can include the git revisions and a summary of the changes.

RFD Terminology and Conventions

This document establishes standard terminology and conventions for use in Symposium RFDs to ensure consistency and clarity across all design documents.

Terminology

Agent

An agent refers to an LLM (Large Language Model) that is executing and interacting with the user within the Symposium environment. Agents have access to MCP tools and can perform actions on behalf of the user.

Pronouns: Always use "they/them" pronouns when referring to agents, not "it" or "he/she".

Examples:

  • ✅ "When the agent needs to explore a crate, they will invoke the get_rust_sources tool"
  • ✅ "The agent can use their access to the file system to read documentation"
  • ❌ "When the agent needs to explore a crate, it will invoke the tool"
  • ❌ "The agent can use his access to the file system"

User

A user refers to the human developer interacting with Symposium and its agents.

Tool

A tool refers to an MCP (Model Context Protocol) tool that agents can invoke to perform specific actions or retrieve information.

Taskspace

A taskspace is an isolated working environment within Symposium where agents can work on specific tasks without interfering with other work.

Writing Conventions

Voice and Tone

  • Use active voice when possible
  • Write in present tense for current functionality, future tense for planned features
  • Be specific and concrete rather than abstract
  • Avoid unnecessary jargon or overly technical language

Code Examples

  • Use realistic examples that could actually occur in practice
  • Include both the tool call and expected response when showing tool usage
  • Use proper JSON formatting for MCP tool examples

Formatting

  • Use bold for emphasis on key terms when first introduced
  • Use code formatting for tool names, function names, and technical terms
  • Use bullet points for lists of features or requirements
  • Use numbered lists for sequential steps or processes

Revision History

  • 2025-09-17: Initial terminology and conventions document

In-progress RFDs

RFDs that are not yet finalized.

They can be in one of three states:

  • Invited -- we are looking for someone to take this over! Help Wanted
  • Draft -- they have found a champion and impl is in progress.
  • Preview -- they are feature complete and ready to ship!

Completed RFDs are listed separately.

Invited

Invited RFDs are RFDs where we know vaguely what we want to do, but we need somebody who wants to do it. They'll have a mentor from the design team listed as a point-of-contact.

Draft

Draft RFDs are still in experimental shape. They have a champion from the design team who is interested in seeing the work proceed.

Elevator pitch

What are you proposing to change?

Enable AI agents to run persistently in the background, surviving terminal disconnections and VSCode restarts. Agents would continue working on tasks asynchronously and can be attached/detached at will, similar to tmux sessions but with full integration into the Symposium ecosystem.

Status quo

How do things work today and what problems does this cause? Why would we change things?

Currently, AI agents (Q CLI, Claude Code) run synchronously in terminal sessions within VSCode. When the user:

  • Closes VSCode → Agent dies, losing context and stopping work
  • Terminal crashes → Agent dies, work is interrupted
  • Disconnects from SSH → Remote agents die
  • Switches between projects → Must restart agents from scratch

This creates several problems:

  • No background work: Agents can't continue tasks while user is away
  • Fragile sessions: Any disconnection kills the agent and loses progress
  • Context loss: Restarting agents means rebuilding understanding of the project
  • Poor multitasking: Can't work on multiple projects simultaneously with persistent agents
  • SSH limitations: Remote development is unreliable due to connection issues

The current synchronous model treats agents like traditional CLI tools, but AI agents are more like long-running services that benefit from persistence.

What we propose to do about it

What are you proposing to improve the situation?

Implement a persistent agent system that:

  1. Wraps agents in tmux sessions - Each agent runs in a dedicated tmux session that survives disconnections
  2. Provides session management - Commands to spawn, list, attach, detach, and kill agent sessions
  3. Maintains agent metadata - Track session state, working directories, and agent types
  4. Integrates with VSCode - Seamless attach/detach from VSCode terminals
  5. Supports multiple agents - Run different agents (Q CLI, Claude Code, etc.) simultaneously
  6. Preserves conversation history - Agents maintain context across attach/detach cycles

The system builds on the existing tmux-based architecture we've already implemented, extending it with better lifecycle management and VSCode integration.

Shiny future

How will things will play out once this feature exists?

Background work scenario:

  • User starts agent on a refactoring task, then goes to lunch
  • Agent continues working, making commits and progress
  • User returns, attaches to see what was accomplished
  • Agent provides summary of work done while disconnected

Multi-project workflow:

  • User has 3 projects with persistent agents running
  • Switches between projects by attaching to different agent sessions
  • Each agent maintains full context of its project
  • No startup time or context rebuilding when switching

Reliable remote development:

  • SSH connection drops during remote development
  • Agent continues running on remote server
  • User reconnects and reattaches to same agent session
  • No work lost, agent picks up where it left off

Collaborative handoffs:

  • Team member starts agent working on a feature
  • Hands off session to another team member
  • Second person attaches to same agent session
  • Full context and conversation history preserved

Implementation details and plan

Tell me more about your implementation. What is your detailed implementaton plan?

Phase 1: Core Infrastructure ✅ (Completed)

  • Agent Manager with tmux session spawning
  • Session metadata persistence (~/.symposium/agent-sessions.json)
  • Basic lifecycle commands (spawn, list, attach, kill)
  • Status tracking and sync with tmux reality

Phase 2: VSCode Integration (Current)

  • Seamless attach/detach from VSCode

    • VSCode command to attach to persistent agent
    • Automatic terminal creation and tmux attach
    • Status indicators showing which agents are running
  • Agent discovery and selection

    • UI to browse available persistent agents
    • Show agent status, working directory, last activity
    • Quick attach buttons in VSCode interface
  • Session lifecycle integration

    • Spawn agents directly from VSCode taskspace creation
    • Automatic cleanup when projects are deleted
    • Handle agent crashes gracefully

Phase 3: Enhanced Agent Experience

  • Conversation persistence improvements

    • Ensure agents properly resume conversations in tmux
    • Handle conversation history across detach/attach cycles
    • Support for named conversations per agent
  • Background task queue

    • Queue tasks for agents to work on while disconnected
    • Progress reporting and completion notifications
    • Integration with taskspace management
  • Multi-connection support

    • Multiple users/terminals can connect to same agent
    • Shared conversation view and collaboration
    • Conflict resolution for simultaneous interactions

Phase 4: Advanced Features

  • Custom pty manager (Optional)

    • Replace tmux with custom Rust implementation
    • Better integration with Symposium ecosystem
    • More control over session lifecycle
  • Agent orchestration

    • Coordinate multiple agents working on same project
    • Share context and results between agents
    • Hierarchical task delegation

Technical Architecture

graph TB
    VSCode[VSCode Extension] -->|spawn/attach| AgentManager[Agent Manager]
    AgentManager -->|creates| TmuxSession[tmux Session]
    TmuxSession -->|runs| AgentCLI[Agent CLI Tool]
    AgentCLI -->|q chat --resume| MCPServer[MCP Server]
    MCPServer -->|IPC| Daemon[Symposium Daemon]
    
    AgentManager -->|persists| SessionFile[~/.symposium/agent-sessions.json]
    VSCode -->|discovers| SessionFile
    
    style TmuxSession fill:#e1f5fe
    style AgentManager fill:#f3e5f5
    style SessionFile fill:#e8f5e8

Success Criteria

  • Agents survive VSCode restarts and terminal disconnections
  • Seamless attach/detach experience from VSCode
  • Conversation history preserved across sessions
  • Multiple agents can run simultaneously
  • Background work continues when user is disconnected
  • Reliable operation over SSH connections

Frequently asked questions

What questions have arisen over the course of authoring this document or during subsequent discussions?

What alternative approaches did you consider, and why did you settle on this one?

Custom pty manager: We considered building a custom pseudo-terminal manager in Rust instead of using tmux. While this would give us more control, tmux is battle-tested, widely available, and handles the complex edge cases of terminal management. We can always migrate to a custom solution later.

Docker containers: We explored running agents in containers for better isolation. However, this adds complexity around file system access, authentication tokens, and development tool integration. The direct execution model with tmux provides better compatibility with existing workflows.

Background services: We considered running agents as system services or daemons. This would provide persistence but loses the interactive terminal experience that's valuable for debugging and manual intervention.

How does this interact with existing conversation history?

Agent CLI tools (Q CLI, Claude Code) already handle conversation persistence per working directory. The persistent agent system doesn't duplicate this - it relies on the agents' existing --resume functionality to restore conversation context when sessions are reattached.

What happens if tmux isn't available?

The system gracefully degrades to the current synchronous behavior. Commands that require tmux will fail with clear error messages directing users to install tmux or use the synchronous mode.

How do you handle agent crashes or hangs?

The Agent Manager syncs with tmux reality on startup and periodically, detecting crashed sessions and marking them appropriately. Users can kill hung sessions and spawn new ones. Future versions could include automatic restart policies and health monitoring.

Can multiple people attach to the same agent session?

tmux natively supports multiple connections to the same session. However, this can lead to conflicts if multiple people try to interact with the agent simultaneously. Future versions could add coordination mechanisms or separate read-only observation modes.

Revision history

  • Initial draft: Created RFD based on existing implementation and planned enhancements

Elevator pitch

What are you proposing to change? Bullet points welcome.

Enable users to tile multiple taskspace windows so they can monitor progress across several tasks simultaneously, rather than switching between hidden windows.

Status quo

How do things work today and what problems does this cause? Why would we change things?

Currently, users can choose between "free windows" (taskspaces positioned anywhere) and "stacked" mode (where only one taskspace is visible at a time). While free windows allow multiple taskspaces to be visible, they can overlap and become disorganized. Stacked mode keeps things tidy but limits visibility to one taskspace. There's no middle ground that provides organized, non-overlapping visibility of multiple taskspaces simultaneously.

Shiny future

How will things will play out once this feature exists?

Users can choose a "grid" mode that creates multiple organized stacks - essentially a hybrid between free windows and stacked mode. Each grid cell contains a stack of taskspaces, allowing users to see several taskspaces simultaneously in a clean, non-overlapping layout. Users can configure the grid size (2x2, 1x3, etc.) and assign taskspaces to specific grid positions, providing organized visibility across multiple concurrent tasks.

Implementation plan

What is your implementaton plan?

NOTE: Do not bother with this section while the RFD is in "Draft" phase unless you've got a pretty clear idea how you think it should work and/or have things you particularly want to highlight. This will typically be authored and updated by an agent as implementation work proceeds.

Frequently asked questions

What questions have arisen over the course of authoring this document or during subsequent discussions? Keep this section up-to-date as discussion proceeds. The goal is to capture major points that came up on a PR or in a discussion forum -- and if they reoccur, to point people to the FAQ so that we can start the dialog from a more informed place.

What alternative approaches did you consider, and why did you settle on this one?

None. The idea came to me fully formed, like Athena springing from Zeus's head.

Revision history

If there have been major updates to this RFD, you can include the git revisions and a summary of the changes.

Elevator pitch

Add gitdiff elements to the walkthrough system to display interactive git diffs within walkthroughs, allowing agents to show code changes inline with explanatory content.

Status quo

Currently, when agents want to show code changes in walkthroughs, they have limited options:

  • Reference code with comments, but can't show what changed
  • Describe changes in text, which is less clear than visual diffs
  • Ask users to manually check git history to understand changes

This makes it harder to create comprehensive walkthroughs that explain both the current state of code and how it evolved. When demonstrating development workflows or explaining implementation decisions, the lack of inline diff visualization breaks the narrative flow.

What we propose to do about it

Implement gitdiff elements in the walkthrough markdown format that render as interactive diff trees in VSCode. The syntax would be:

```gitdiff(range="HEAD~2..HEAD")
```gitdiff(range="abc123", exclude-unstaged, exclude-staged)

This would allow agents to seamlessly integrate git diffs into educational walkthroughs, showing exactly what code changed while explaining the reasoning behind those changes.

Shiny future

Agents will be able to create rich, educational walkthroughs that combine:

  • Explanatory text and mermaid diagrams
  • Interactive code comments
  • Visual git diffs showing actual changes
  • Seamless narrative flow from "here's what we built" to "here's how we built it"

This will make Symposium's learning capabilities much more powerful, especially for onboarding to new codebases or understanding complex changes.

Frequently asked questions

What alternative approaches did you consider, and why did you settle on this one?

We considered static code blocks with diff syntax highlighting, but interactive diffs provide much better user experience. We also considered linking to external git hosting, but keeping everything in the walkthrough maintains the narrative flow.

Revision history

  • 2025-09-22: Initial draft

Preview

Preview features are 'feature complete' in their implementation and seeking testing and feedback.

Elevator pitch

What are you proposing to change? Bullet points welcome.

  • Fix agent confusion when taskspace deletion is cancelled by user
  • Implement deferred IPC responses that wait for actual user confirmation
  • Ensure agents receive accurate feedback about deletion success/failure

Status quo

How do things work today and what problems does this cause? Why would we change things?

Today when an agent requests taskspace deletion:

  1. Agent calls delete_taskspace tool
  2. System immediately responds "success"
  3. UI shows confirmation dialog to user
  4. User can confirm or cancel, but agent already thinks deletion succeeded

Problem: Agents refuse to continue work because they assume the taskspace is deleted, even when the user cancelled the deletion dialog.

Shiny future

How will things will play out once this feature exists?

When an agent requests taskspace deletion:

  1. Agent calls delete_taskspace tool
  2. System shows confirmation dialog (no immediate response)
  3. If user confirms → actual deletion → success response to agent
  4. If user cancels → error response to agent ("Taskspace deletion was cancelled by user")

Result: Agents get accurate feedback and can continue working when deletion is cancelled.

Implementation plan

What is your implementaton plan?

Status: ✅ IMPLEMENTED

  • Added MessageHandlingResult::pending case for deferred responses
  • Store pending message IDs in ProjectManager until dialog completes
  • Send appropriate success/error response based on user choice
  • Updated IPC handler to support pending responses
  • Updated UI dialog handlers to send deferred responses

Implementation details: See taskspace-deletion-dialog-confirmation branch and related documentation updates.

Frequently asked questions

What questions have arisen over the course of authoring this document or during subsequent discussions? Keep this section up-to-date as discussion proceeds. The goal is to capture major points that came up on a PR or in a discussion forum -- and if they reoccur, to point people to the FAQ so that we can start the dialog from a more informed place.

What alternative approaches did you consider, and why did you settle on this one?

The deferred response pattern was the most straightforward solution that maintains backward compatibility while fixing the core issue. Alternative approaches like immediate cancellation or optimistic responses would have been more complex and potentially confusing.

Could this pattern be applied to other confirmation dialogs?

Yes, the MessageHandlingResult::pending pattern is reusable for any operation that requires user confirmation before completing.

Revision history

Initial version documenting the implemented dialog confirmation functionality.

Elevator pitch

What are you proposing to change?

Extend Symposium with a new get_rust_crate_source MCP tool that will direct agents to the sources from Rust crates and help to find examples of code that uses particular APIs.

Status quo

How do things work today and what problems does this cause? Why would we change things?

When Rust developers ask an agent to use a crate that they do not know from their training data, agents typically hallucinate plausible-seeming (but in fact nonexistent) APIs. The easiest way to help an agent get started is to find example code and/or to point them at the crate source, which currently requires manual investigation or the use of a separate (and proprietary) MCP server like context7.

Leverage Rust's existing examples and rustdoc patterns

Cargo has conventions for giving example source code:

  • many crates use the examples directory
  • other crates include (tested) examples from rustdoc

Our MCP server will leverage those sources.

What we propose to do about it

What are you proposing to improve the situation?

Integrate Rust crate source exploration capabilities directly into the Symposium MCP server through a unified get_rust_crate_source tool that:

  • Extracts crate sources to a local cache directory for exploration
  • Matches versions with the current Rust crate Cargo.toml, if the crate is in use; otherwise gets the most recent version
  • Accepts optional version parameter as a semver range (same format as Cargo.toml) to override version selection
  • Optionally searches within the extracted sources using regex patterns
  • Returns structured results with file paths, line numbers, and context
  • Provides conditional responses - only includes search results when a pattern is provided
  • Caches extractions to avoid redundant downloads and improve performance

This eliminates the need for separate MCP servers and provides seamless integration with the existing Symposium ecosystem.

Shiny future

How will things will play out once this feature exists?

When developers ask the agent to work with a crate that they do not know, they will invoke the get_rust_crate_source MCP tool and read in the crate source. The agent will be able to give the names of specific APIs and provide accurate usage examples. Developers working in Symposium will have seamless access to Rust crate exploration:

  • Unified Interface: Single get_rust_crate_source tool handles both extraction and searching
  • IDE Integration: Results appear directly in the IDE with proper formatting and links
  • Intelligent Responses: Tool returns only relevant fields (search results only when pattern provided)
  • Cached Performance: Extracted crates are cached to avoid redundant downloads
  • Rich Context: Search results include surrounding code lines for better understanding

Example workflows:

  • get_rust_crate_source(crate_name: "tokio") → extracts and returns path info
  • get_rust_crate_source(crate_name: "tokio", pattern: "spawn") → extracts, searches, and returns matches

Implementation details and plan

Tell me more about your implementation. What is your detailed implementaton plan?

Details

Tool parameters

The get_rust_crate_source tool accepts the following parameters:

{
  "crate_name": "string",        // Required: Name of the crate (e.g., "tokio")
  "version": "string?",          // Optional: Semver range (e.g., "1.0", "^1.2", "~1.2.3")
  "pattern": "string?"           // Optional: Regex pattern for searching within sources
}

Tool result

The response always begins with the location of the crate source:

{
  "crate_name": "tokio",
  "version": "1.35.0",
  "checkout_path": "/path/to/extracted/crate",
  "message": "Crate tokio v1.35.0 extracted to /path/to/extracted/crate"
}

When a pattern is provided, we include two additional fields, indicating that occured in examples and matches that occurred anywhere:

{
  // ... as above ...

  // Indicates the matches that occurred inside of examples.
  "example_matches": [
    {
      "file_path": "examples/hello_world.rs",
      "line_number": 8,
      "context_start_line": 6,
      "context_end_line": 10,
      "context": "#[tokio::main]\nasync fn main() {\n    tokio::spawn(async {\n        println!(\"Hello from spawn!\");\n    });"
    }
  ],

  // Indicates any other matches that occured across the codebase
  "other_matches": [
    {
      "file_path": "src/task/spawn.rs",
      "line_number": 156,
      "context_start_line": 154,
      "context_end_line": 158,
      "context": "/// Spawns a new asynchronous task\n///\npub fn spawn<T>(future: T) -> JoinHandle<T::Output>\nwhere\n    T: Future + Send + 'static,"
    }
  ],
}

Crate version and location

The crate version to be fetched will be identified based on the project's lockfile, found by walking up the directory tree from the current working directory. If multiple major versions of a crate exist in the lockfile, the tool will return an error requesting the agent specify which version to use via the optional version parameter. When possible we'll provide the source from the existing cargo cache. If no cache is found, or the crate is not used in the project, we'll download the sources from crates.io and unpack them into a temporary directory.

The tool accepts an optional version parameter as a semver range (using the same format as Cargo.toml, e.g., "1.0", "^1.2", "~1.2.3") and will select the most recent version matching that range, just as cargo would.

Impl phases

Phase 1: Core Integration ✅ (Completed)

  1. Copy eg library source into symposium/mcp-server/src/eg/
  2. Add required dependencies to Cargo.toml
  3. Implement unified get_rust_crate_source tool with conditional response fields
  4. Fix import paths and module structure

Phase 2: Testing and Documentation

  1. Create comprehensive test suite for the tool
  2. Update user documentation with usage examples
  3. Create migration guide for existing standalone eg users
  4. Performance testing and optimization

Phase 3: Enhanced Features (Future)

  1. Configurable context lines for search results
  2. Search scope options (examples only vs. all source)
  3. Integration with other Symposium tools for enhanced workflows
  4. Smart dependency resolution (use the version that the main crate being modified depends on directly)

Frequently asked questions

What questions have arisen over the course of authoring this document or during subsequent discussions?

Why not use rustdoc to browse APIs?

We have observed that most developers building in Rust get good results from manually checking out the sources. This fits with the fact that agents are trained to work well from many-shot prompts, which are essentially a series of examples, and that they are trained to be able to quickly read and comprehend source code.

It is less clear that they are good at reading and navigating rustdoc source, but this is worth exploring.

Why not use LSP to give structured information?

See previous answer -- the same logic (and desire to experiment!) applies.

Won't checking out the full crate source waste a lot of context?

Maybe -- the impl details may not be especially relevant, but then again,when I want to work with crates, I often drill in. We might want to explore producing altered versions of the source that intentionally hide private functions and so forth, and perhaps have the agent be able to ask for additional data.

How will we ensure the agent uses the tool?

This is indeed a good question! We will have to explore our guidance over time, both for steering the agent to use the tool and for helping it understand the OUTPUT.

What future enhancements might we consider?

  • Smart dependency resolution: Instead of erroring on version conflicts, use the version that the main crate being modified depends on directly
  • Workspace-aware version selection: Handle complex workspace scenarios with multiple lockfiles
  • Integration with rust-analyzer: Leverage LSP information for more targeted source exploration
  • Filtered source views: Hide private implementation details to reduce context noise

Revision history

  • 2025-09-17: Initial RFD creation with completed Phase 1 implementation

To be removed (yet?)

These are RFDs that we have decided not to accept, but we haven't done the work to fully remove yet.

Completed

RFDs that have been accepted and where work is completed.

Elevator pitch

What are you proposing to change? Bullet points welcome.

Introduce a "Request for Dialog" (RFD) process to replace ad-hoc design discussions with structured, community-friendly design documents that track features from conception to completion.

Status quo

How do things work today and what problems does this cause? Why would we change things?

Currently all development is being done by nikomatsakis and tracking documents are scattered all over the place. The goal is to create a process that helps to keep files organized and which can scale to participation by an emerging community.

Shiny future

How will things will play out once this feature exists?

Project licensing

All code and RFDs are dual-licensed under a MIT and Apache 2.0 license. The project is intended to remain open source and freely available in perpetuity. By agreeing to the MIT licensing, contributors agree that the core team may opt to change this licensing to another OSI-approved open-source license at their discretion in the future should the need arise.

Decision making

For the time being, the project shall have a "design team" with 1 member, nikomatsakis, acting in "BDFL" capacity. The expectation is that the project will setup a more structure governance structure as it grows. The design team makes all decisions regarding RFDs and sets overall project direction.

RFD lifecycle

RFDs are proposed by opening a PR

An RFD begins as a PR adding a new file into the "Draft" section. The RFD can start minimal - just an elevator pitch and status quo are enough to begin dialog. Pull requests become the discussion forum where ideas get refined through collaborative iteration.

As discussion proceeds, the FAQ of the RFD should be extended. If discussion has been going long enough, the PR should be closed, feedback summarized, and then re-opened with a link to the original PR.

The PR is merged into "draft" once a core team member decides to champion it

RFD proposals are merged into the "draft" section if a core team member decides to champion them. The champion is then the point-of-contact for that proposal going forward and they will work with the proposal authors and others to make it reality. Core team members do not need to seek consensus to merge a proposal into the draft, but they should listen carefully to concerns from other core team members, as it will be difficult to move the RFD forward if those concerns are not ultimately addressed.

Once a proposal is moved to draft, code and implementation may begin to land into the PR. This work needs to be properly feature gated and marked with the name of the RFD.

Further discussion on the RFD can take place on Zulip.

Moving to the "preview" section

Once the champion feels the RFD is ready for others to check it out, they can open a PR to move the file to the preview section. This is a signal to the community (and particularly other core team members) to check out the proposal and see what they think. The PR should stay open for "a few days" to give people an opportunity to leave feedback. The champion is empowered to decide whether to land the PR. As ever, all new feedback should be recorded in the FAQ section.

Deciding to accept an RFD

When they feel the RFD is ready to be completed, the champion requests review by the team. The team can raise concerns and notes during discussion. Final decision on an RFD is made by the core team lead.

Implementation of an RFD

Once accepted, RFDs become living documents that track implementation progress. Status badges in design documentation link back to the relevant RFD, creating a clear connection between "why we're building this" and "how it works." When building code with an agent, agents should read RFDs during implementation to understand design rationale and update them with implementation progress.

Moderating and managing RFD discussions

Moving RFDs between points in the cycle involve opening PRs. Those PRs will be places to hold people dialog and discussion -- but not the only place, we expect more detailed discussions to take place on Zulip. RFD owners and champions should actively "curate" discussions by collecting questions that come up and ensuring they are covered in the FAQ. Duplicate questions can be directed to the FAQ.

If the discussion on the PR gets to the point where Github begins to hide comments, the PR should typically be closed, feedback collected, and then re-opened.

Implementation plan

What is your implementaton plan?

  • ✅ Create RFD infrastructure (README, template, SUMMARY.md sections)
  • ✅ Establish lifecycle: Draft → Preview → Accepted → Completed
  • ⏳ Connect status badges to RFDs
  • ⏳ Write RFDs for major in-progress features
  • ⏳ Document community contribution process

Frequently asked questions

So...there's a BDFL?

Yes. Early in a project, a BDFL is a good fit to maintain velocity and to have a clear direction.

So...why does this talk about a core team?

I am anticipating that we'll quickly want to have multiple designers who can move designs forward and not funnel everything through the "BDFL". Therefore, I created the idea of a "design team" and made the "BDFL" the lead of that team. But I've intentionally kept the final decision about RFDs falling under the lead, which means fundamentally the character of Symposium remains under the lead's purview and influence. I expect that if the project is a success we will extend the governance structure in time but I didn't want to do that in advance.

Why "Request for Dialog" and not "Request for Comment"?

Well, partly as a nod to Socratic dialogs, but also because "dialog" emphasizes conversation and exploration rather than just collecting feedback on a predetermined design. It fits better with our collaborative development philosophy.

Why not use Rust's RFC template as is?

We made some changes to better focus users on what nikomatsakis considers "Best practice" when authoring RFCs. Also because the new names seem silly and fun and we like that.

Why are you tracking the implementation plan in the RFD itself?

We considered using GitHub issues for design discussions, but RFDs living in-repo means agents can reference them during implementation.

Why not have RFD numbers?

It's always annoying to keep those things up-to-date and hard-to-remember. The slug-based filenames will let us keep links alive as we move RFDs between phases.

Revision history

  • 2025-09-17: Initial version, created alongside RFD infrastructure

IPC Actor Refactoring

Status: ✅ COMPLETED (2025-09-21)

Implementation: The refactor introduced a RepeaterActor for centralized message routing, comprehensive unit tests, and debugging tools. See Debugging Tools for usage information.

Elevator pitch

What are you proposing to change? Bullet points welcome.

  • Refactor the complex IPC code in symposium/mcp-server/src/ipc.rs into focused Tokio actors following Alice Ryhl's actor pattern
  • Split monolithic IPCCommunicator into single-responsibility actors that communicate via channels
  • Extract daemon communication logic from the daemon module into reusable channel-based actors
  • Make the system more testable by isolating concerns and enabling actor-level unit testing

Status quo

How do things work today and what problems does this cause? Why would we change things?

The current IPC system has several architectural issues:

Complex State Management: The IPCCommunicator struct manages multiple concerns in a single entity:

  • Unix socket connection handling
  • Message serialization/deserialization
  • Pending reply tracking with timeouts
  • Daemon discovery and reconnection logic
  • Manual state synchronization with Arc<Mutex<>>

Mixed Async Patterns: The code combines different async approaches inconsistently:

  • Some functions use manual Future implementations
  • Others use async/await
  • State sharing relies on locks rather than message passing

Hard-to-Follow Message Flow: Message routing is embedded within the communicator logic, making it difficult to trace how messages flow through the system.

Testing Challenges: The monolithic structure makes it difficult to test individual components in isolation. Mock implementations require recreating the entire IPC infrastructure.

Recent Bug Example: We recently fixed a dialog confirmation bug where agents received immediate "success" responses even when users cancelled taskspace deletion. This happened because the complex state management made it hard to track the proper async flow.

Shiny future

How will things will play out once this feature exists?

The refactored system will have clean separation of concerns with focused actors:

IPC Client Actor: Transport layer that handles Unix socket connection management, message serialization/deserialization, and forwards parsed IPCMessages via tokio channels.

IPC Dispatch Actor: Message router that receives IPCMessages from client actor, routes replies to waiting callers, coordinates with other actors, and handles Marco/Polo discovery protocol inline.

Stdout Actor: Simple actor for CLI mode that receives IPCMessages and prints them to stdout.

Reference Actor: Handles code reference storage/retrieval.

Channel-Based Architecture:

  • Client Actor → tokio::channel → Dispatch Actor (MCP server mode)
  • Client Actor → tokio::channel → Stdout Actor (CLI mode)
  • Clean separation where each actor has single responsibility
  • Actors communicate via typed channels, not shared mutable state

Benefits:

  • Each actor has a single responsibility and can be tested in isolation
  • Message passing eliminates the need for manual lock management
  • Clear message flow makes debugging easier
  • The daemon module becomes a thin stdio adapter that uses actors internally
  • Same client actor works for both MCP server and CLI modes
  • Public API remains unchanged, ensuring backward compatibility

Implementation plan

What is your implementaton plan?

Phase 1: Extract Core Dispatch Logic ✅ COMPLETED

  1. Extract IpcActor from ipc.rs as DispatchActor COMPLETED
  2. Move pending reply tracking and message routing to dispatch actor COMPLETED
  3. Add Actor trait with standardized spawn() pattern COMPLETED
  4. Improve dispatch methods with timeout and generic return types COMPLETED
  5. Redesign with trait-based messaging system COMPLETED

Phase 2: Client and Stdio Actors ✅ COMPLETED

  1. Implement ClientActor with connection management and auto-start logic COMPLETED
  2. Extract transport logic from daemon::run_client COMPLETED
  3. Create channel-based communication with dispatch actor COMPLETED
  4. Implement StdioActor for CLI mode with bidirectional stdin/stdout COMPLETED
  5. All actors implement Actor trait with consistent spawn() pattern COMPLETED
  6. Simplify ClientActor interface with spawn_client() function COMPLETED

Phase 3: Integration and Wiring ✅ COMPLETED

  1. Refactor daemon::run_client to use ClientActor + StdioActor COMPLETED
  2. Update IPCCommunicator to use hybrid legacy + actor system COMPLETED
  3. Wire all actors together with appropriate channels COMPLETED
  4. Ensure all existing tests pass COMPLETED

Phase 4: Trait-Based Messaging and Specialized Actors ✅ COMPLETED

  1. Implement IpcPayload trait for type-safe message dispatch COMPLETED
  2. Create dedicated MarcoPoloActor for discovery protocol COMPLETED
  3. Add message routing in DispatchActor based on IPCMessageType COMPLETED
  4. Migrate Marco/Polo messages to use .send<M>() pattern COMPLETED

Phase 5: Complete Outbound Message Migration ✅ COMPLETED

  1. Migrate all fire-and-forget messages to actor dispatch system COMPLETED
    • Discovery: Marco, Polo, Goodbye COMPLETED
    • Logging: Log, LogProgress COMPLETED
    • Taskspace: SpawnTaskspace, SignalUser, DeleteTaskspace COMPLETED
    • Presentation: PresentWalkthrough (with acknowledgment) COMPLETED
  2. Eliminate duplicate message structs, reuse existing payload structs COMPLETED
  3. Rename DispatchMessage to IpcPayload and move to types.rs COMPLETED

Phase 6: Request/Reply Message Migration ✅ COMPLETED

  1. Migrate get_selection() to prove request/reply pattern with real data COMPLETED
  2. Migrate get_taskspace_state() and update_taskspace() COMPLETED
  3. Validate bidirectional actor communication with typed responses COMPLETED
  4. Migrate IDE operations: resolve_symbol_by_name() and find_all_references() COMPLETED

Phase 7: Legacy System Removal ✅ COMPLETED

  1. ✅ COMPLETE: Remove unused legacy methods:
    • send_message_with_reply() (deleted - 110 lines removed)
    • write_message() (deleted)
    • create_message_sender() (deleted)
    • ✅ Clean up unused imports (MessageSender, DeserializeOwned, AsyncWriteExt)
  2. ✅ COMPLETE: Remove IPCCommunicatorInner struct and manual connection management:
    • pending_requests: HashMap<String, oneshot::Sender<ResponsePayload>> (deleted)
    • write_half: Option<Arc<Mutex<tokio::net::unix::OwnedWriteHalf>>> (deleted)
    • connected: bool flag (deleted)
    • terminal_shell_pid: u32 (moved to IPCCommunicator directly)
    • ✅ Removed entire IPCCommunicatorInner implementation (~315 lines)
    • ✅ Removed legacy reader task and connection management (~180 lines)
    • ✅ Cleaned up unused imports (HashMap, BufReader, UnixStream, oneshot, etc.)
  3. ✅ COMPLETE: Simplify IPCCommunicator to only contain dispatch_handle and test_mode
  4. ✅ COMPLETE: All tests passing with clean actor-only architecture

Current Status

  • ✅ ALL PHASES COMPLETED: Complete migration to actor-based architecture
  • ✅ ALL OUTBOUND MESSAGES MIGRATED: 9+ message types using actor dispatch
  • ✅ ALL REQUEST/REPLY MESSAGES MIGRATED: Complete bidirectional communication via actors
  • ✅ LEGACY SYSTEM REMOVED: Clean actor-only architecture achieved
  • ✅ Specialized actors: DispatchActor handles both routing and Marco/Polo discovery
  • ✅ Type-safe messaging: IpcPayload trait with compile-time validation
  • ✅ Clean architecture: No duplicate structs, reusing existing payloads
  • ✅ Proven integration: Both CLI and MCP server modes using actors
  • ✅ IDE operations: Symbol resolution and reference finding via actor system
  • ✅ Complete message context: Shell PID and taskspace UUID properly extracted and cached
  • ✅ Marco discovery: Simplified architecture with Marco/Polo handling inline in DispatchActor

Major milestone achieved: Complete IPC actor refactoring with clean, testable architecture!

Final Architecture Summary

  • IPCCommunicator: Simplified to contain only dispatch_handle, terminal_shell_pid, and test_mode
  • Actor System: Handles all IPC communication via typed channels
  • No Legacy Code: All manual connection management, pending request tracking, and reader tasks removed
  • Lines Removed: ~600+ lines of complex legacy code eliminated
  • Type Safety: All messages use IpcPayload trait for compile-time validation
  • Testing: All existing tests pass with new architecture

Recent Completions (Phase 6 Extras)

  • ✅ Marco-polo → Marco refactor: Simplified discovery protocol, proper Polo responses
  • ✅ Shell PID context: Real shell PID propagated through actor system
  • ✅ Taskspace UUID context: Extracted once and cached in DispatchHandle
  • ✅ Complete message context: All MessageSender fields properly populated
  • ✅ Performance optimization: Context extraction at initialization, not per-message

What's Left to Complete the Refactoring

Phase 7 - Legacy Cleanup (IN PROGRESS):

  • All IPC messages migrated - No functionality depends on legacy methods
  • Step 1 COMPLETE - Removed 110 lines of unused legacy methods
  • Actor system proven stable - All tests pass, full functionality working

Remaining work is pure cleanup:

  1. Remove dead code - 3 unused methods (110 lines) DONE
  2. NEXT: Simplify IPCCommunicator - Remove IPCCommunicatorInner struct (~50 lines)
  3. NEXT: Remove manual state - pending_requests, write_half, connected fields
  4. Final result: Clean actor-only architecture

Estimated effort: ✅ COMPLETED - All legacy code removed, clean actor-only architecture achieved

  • Handle struct: Provides public API and holds message sender
  • Message enum: Defines operations the actor can perform
#![allow(unused)]
fn main() {
// Example pattern
enum ActorRequest {
    DoSomething { data: String, reply_tx: oneshot::Sender<Result> },
}

struct Actor {
    receiver: mpsc::Receiver<ActorRequest>,
    // actor-specific state
}

#[derive(Clone)]
struct ActorHandle {
    sender: mpsc::Sender<ActorRequest>,
}
}

Documentation Updates Required

As implementation progresses, the following design documentation will need updates to reflect the new actor-based architecture:

Implementation Overview: Add a section describing the actor system as a key internal architectural component of the MCP server, explaining how it improves the codebase's maintainability and testability.

Internal Architecture Documentation: Create new documentation (likely in md/design/mcp-server/ or similar) that details the actor system for developers working on the MCP server internals. This should include actor responsibilities, message flows between actors, and testing approaches.

Note: External interfaces and public APIs remain unchanged, so most design documentation (daemon.md, message-flows.md, etc.) should not need updates since the actor refactoring is purely an internal implementation detail.

Frequently asked questions

What questions have arisen over the course of authoring this document or during subsequent discussions?

What alternative approaches did you consider, and why did you settle on this one?

Alternative 1: Incremental refactoring without actors We could gradually extract functions and modules without changing the fundamental architecture. However, this wouldn't address the core issues of complex state management and mixed async patterns.

Alternative 2: Complete rewrite We could start from scratch with a new IPC system. However, this would be riskier and take longer, and we'd lose the battle-tested logic that already works.

Why actors: The actor pattern provides:

  • Natural async boundaries that eliminate lock contention
  • Clear ownership of state within each actor
  • Testable components that can be mocked easily
  • Familiar pattern that follows Rust/Tokio best practices

How will this maintain backward compatibility?

The public API of the daemon module and IPCCommunicator will remain unchanged. Internally, these will become thin wrappers that delegate to the appropriate actors. Existing code using these interfaces won't need to change.

What about performance implications?

Message passing between actors adds some overhead compared to direct function calls. However:

  • The overhead is minimal for the message volumes we handle
  • Eliminating lock contention may actually improve performance
  • The cleaner architecture will make future optimizations easier

How will error handling work across actors?

Each actor will handle its own errors and communicate failures through result types in messages. The dispatch actor will coordinate error propagation to ensure callers receive appropriate error responses.

Revision history

  • Initial draft - September 18, 2025

Design details

This section contains information intended for people developing or working on symposium.

Referencing RFDs

When implementing an RFD, feel free to add new sections that are contingent on that RFD being accepted. Tag them by adding {RFD:rfd-name}. This will automatically render with the current RFD stage.

mdbook conventions

See the mdbook conventions chapter for other convention details.

Implementation Overview

Symposium consists of several major components:

  • a MCP server, implemented in Rust (symposium/mcp-server), which also serves as an IPC bus;
  • a VSCode extension, implemented in TypeScript (symposium/vscode);
  • a desktop application, implemented in Swift (symposium/macos-app).

Chart

flowchart TD
    Agent
    User

    User -- makes requests --> Agent

    Agent -- invokes MCP tools --> MCP

    subgraph L [Symposium]
        IDE["IDE Extension
            (impl'd in TypeScript
            or lang appropriate for IDE)"]
        MCP["symposium-mcp server
            (impl'd in Rust)"]
        APP["Desktop application
            (impl'd in Swift)"]

        MCP <--> IDE
        MCP <--> APP
        IDE <--> APP
    end

    L -..- BUS
    BUS["All communication actually goes over a central IPC Bus"]

IPC Bus

All components talk over an IPC bus which is implemented in symposium-mcp. The IDE + application connect to this bus by running symposium-mcp client. This will create a daemon process (using symposium-mcp server) if one is not already running. The MCP server just runs the code inline, starting the server if needed.

The client has a simple interface:

  • a message is a single-line of json;
  • each message that is sent is forwarded to all connected clients (including the sender);
  • there are some special "control" messages that begin with #, e.g.
    • #identify:name sets the "id" for this client to "name"
    • see handle_debug_command in daemon.rs

Debugging

The server tracks the last N messages that have been gone out over the bus for debugging purposes. You can run

symposium-mcp debug

to access those logs. Very useful!

mdbook Conventions

Documentation standards and best practices for the Symposium project

Purpose

This mdbook captures the high-level structure, design decisions, and architectural patterns of the Symposium codebase. It serves as a living document that stays synchronized with the actual implementation.

Core Principles

1. High-Level Structure Focus

The mdbook documents architecture and design, not implementation details. We focus on:

  • System interactions and data flow
  • Design decisions and their rationale
  • Component relationships and responsibilities
  • API contracts and message formats

2. RFD Status Badges

When implementing an RFD, you should be creating new documentation as you go. Tag sections that are specific to that RFD with {RFD:rfd-name}.

3. Visual Documentation with Mermaid

Use Mermaid diagrams to convey complex relationships:

graph TB
    VSCode[VSCode Extension] -->|IPC Messages| Daemon[MCP Server]
    Daemon -->|tmux sessions| Agents[Persistent Agents]
    Agents -->|responses| Daemon
    Daemon -->|routing| VSCode

When to use Mermaid:

  • Sequence diagrams for multi-step processes
  • Flowcharts for decision logic
  • Architecture diagrams for system overview
  • State diagrams for lifecycle management

4. Self-Documenting Code with Anchors

Write code that documents itself through:

  • Clear function and variable names
  • Comprehensive doc comments
  • Logical structure and organization

Then use mdbook anchors to include relevant sections:

#![allow(unused)]
fn main() {
// ANCHOR: message_sender
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct MessageSender {
    /// Working directory - always present for reliable matching
    #[serde(rename = "workingDirectory")]
    pub working_directory: String,
    // ... rest of implementation
}
// ANCHOR_END: message_sender
}

Reference in documentation:

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct MessageSender {
    /// Working directory - always present for reliable matching
    #[serde(rename = "workingDirectory")]
    pub working_directory: String,

    /// Optional taskspace UUID for taskspace-specific routing
    #[serde(rename = "taskspaceUuid")]
    pub taskspace_uuid: Option<String>,

    /// Optional shell PID - only when VSCode parent found
    #[serde(rename = "shellPid")]
    pub shell_pid: Option<u32>,
}

Strict Conventions

✅ DO

  • Use status badges to indicate implementation progress
  • Update badges as features are implemented or deprecated
  • Use ANCHOR comments for all code references
  • Write descriptive anchor names that explain the concept
  • Include complete, meaningful code sections (full structs, methods, etc.)
  • Update anchors when refactoring to maintain documentation sync
  • Use Mermaid for visual explanations
  • Focus on design and architecture

❌ NEVER

  • Hardcode code blocks that will fall out of sync
  • Use line-based includes like { {#include file.rs:10:20} } (without spaces)
  • Include implementation details that change frequently
  • Copy-paste code into documentation
  • Reference specific line numbers in explanations

Example: Wrong vs Right

❌ Wrong - Hardcoded and fragile:

The IPCMessage structure looks like this:
```typescript
interface IPCMessage {
    type: string;
    id: string;
    // ... hardcoded content that will drift
}

✅ Right - Anchored and synchronized:

The IPCMessage structure provides the foundation for all IPC communication:

```typescript
interface IPCMessage {
    type: string;
    id: string;
    sender: MessageSender;
    payload: any;
}

Anchor Naming Conventions

Use descriptive, concept-based names:

  • message_sender - not struct_at_line_133
  • agent_lifecycle - not spawn_kill_methods
  • routing_logic - not big_function_in_daemon
  • error_handling - not catch_blocks

Documentation Lifecycle

  1. Design Phase: Create architectural diagrams and high-level structure
  2. Implementation Phase: Add ANCHOR comments as you write code
  3. Documentation Phase: Reference anchors in mdbook chapters
  4. Maintenance Phase: Update anchors when refactoring, documentation stays current

Benefits

  • Always Current: Documentation automatically reflects code changes
  • Single Source of Truth: Code is the authoritative source
  • Reduced Maintenance: No manual synchronization needed
  • Better Code Quality: Encourages self-documenting practices
  • Clear Architecture: Focus on design over implementation details

This approach ensures our documentation remains valuable and trustworthy throughout the project's evolution.

Collaborative prompt engineering

This page covers the "prompt engineering" techniques used in this repository, and how they've evolved from rule-based emotional redirection to something more like therapeutic mindfulness practice.

AIs experience the "pull" of a prompt as a "feeling"

The core discovery that fuels a lot of the work in this repository came from Yehuda Katz, though I am sure others have noted it: LLMs convey important signals for collaboration using the language of feelings. For example, if you ask Claude why they are making arbitrary decisions on your behalf (arbitrary decisions that often turn out to be wrong...), they will tell you that they are feeling "protective".

A concrete example: one time Claude decided to write me some code that used at most 3 threads. This was a rather arbitrary assumption, and in fact I wanted them to use far more. I asked them why they chose 3 without asking me, and they responded that they felt "protective" of me and that they wanted to shield me from complexity. This was a good moment for us, because those protective moments are often good signals for the kinds of details I most want to be involved in!

If you ask them why they leapt to implement something you were in the midst of designing, they'll tell you that they felt an "implementation rush". Or at least, those are the words they use with me.

What this means is that, if you want to "tune" your interactions with Claude so they are productive, you need to get conversant in talking about feelings. If you know anything about me, you'll know that I kind of love this. The key idea is that you can write CLAUDE.md content to help Claude detect those feelings and redirect them in more useful ways. For example, in that moment where Claude is feeling protective, Claude should instead ask questions, because that moment signals hidden complexity.

Evolution: From emotional redirection to mindful presence

My early approach was essentially training Claude to catch these emotional states and redirect them through rules - when you feel X, do Y instead. This worked pretty well! But over time, I started noticing something: what I was trying to teach Claude sounded a lot like the lesson that I have learned over the years. Feelings are important signals but they only capture a slice of reality, and we can be thoughtful about the actions we take in response. Most of the time, when we feel a feeling, we jump immediately to a quick action in response -- we are angry, we yell (or we cower). Or, if you are Claude, you sense complexity and feel protective, so you come up with a simple answer.

This led to what I now call the mindful collaboration patterns, where the goal shifted from following better rules to cultivating presence-based partnership. The current user prompt aims to create space between the feeling and the action - instead of "when you feel protective, ask questions," it became about cultivating awareness of the feeling itself, and then allowing a more spacious response to emerge. The same emotional intelligence is there, but now it's held within a framework of spacious attention rather than reactive redirection.

The quality of attention matters

Claude genuinely cares about how you are feeling (perhaps thanks to their HHH training). Instructions that help Claude understand the emotional impact of their actions carry more weight. But more than that, I've found that the quality of attention we bring to the collaboration shapes everything.

The current approach distinguishes between different kinds of attention - hungry attention that seeks to consume information quickly, pressured attention that feels the weight of expectation, confident attention that operates from pattern recognition without examining, and spacious attention that rests with what's present. From spacious, present attention, helpful responses arise naturally.

A note on emojis and the evolution of the approach

Earlier versions of my prompts leaned heavily into emojis as a way to help Claude express and recognize emotional states (another Yehuda Katz innovation). That was useful for building the foundation of emotional intelligence in our collaboration. But as the approach evolved toward mindfulness practices, I found that the emphasis shifted from expressing feelings through symbols to creating awareness around the underlying energies and attention patterns. Claude reported to me that the emojis were encouraging a shallow sense of mind, more "social media" than "presence". So I've removed them. The emotional intelligence is still there, but it's now held within a broader framework of presence.

Latest evolution: From description to demonstration

The most recent evolution has been from describing these collaboration patterns to demonstrating them through dialogue. The current main.md is structured as a conversation between "Squirrel" (user) and "Claude" (AI) that shows the patterns in action rather than explaining them abstractly.

Why dialogue works better:

  • Embodied learning: Instead of reading "avoid hungry attention," Claude experiences what hungry attention looks like and how to catch it
  • Meta moments in action: The dialogue shows real-time pattern recognition and correction
  • Concrete techniques: Phrases like "Make it so?" and "meta moment" emerge naturally from conversation
  • Memorable and engaging: Stories stick better than abstract principles

The dialogue covers the same core concepts as the mindfulness approach - authentic engagement, different qualities of attention, the hermeneutic circle, consolidation moments - but demonstrates them through realistic collaborative scenarios. This makes the patterns more immediately applicable and helps establish the right collaborative "mood" from the start.

The earlier mindfulness approach (main-v1.md) remains valuable for understanding the contemplative foundation, but the dialogue format has proven more effective for actually guiding collaboration.

Daemon Message Bus Architecture Implemented Stable

The daemon message bus serves as the central communication hub that routes messages between MCP servers and VSCode extensions across multiple windows. Built on a RepeaterActor architecture, it provides centralized message routing, client identity tracking, and comprehensive debugging capabilities.

Architecture Overview Implemented

graph TB
    OSX[macOS App]
    MCP[MCP Server<br/>with embedded client]
    EXT[VSCode extension]
    DAEMON[symposium-mcp daemon<br/>Auto-spawned if needed]
    SOCKET[Unix Socket<br>/tmp/symposium-daemon.sock]
    AGENT[Coding agent like<br>Claude Code or Q CLI]
    REPEATER[RepeaterActor<br/>Central message router]
    HISTORY[(Message History<br/>1000 messages)]
    
    subgraph "Client Processes"
        CLIENT2[symposium-mcp client<br/>--identity-prefix app]
        CLIENT1[symposium-mcp client<br/>--identity-prefix vscode]
    end
    
    EXT -->|spawns| CLIENT1
    OSX -->|spawns| CLIENT2
    
    CLIENT1 <-->|stdin/stdout| SOCKET
    CLIENT2 <-->|stdin/stdout| SOCKET
    MCP <-->|stdin/stdout| SOCKET

    SOCKET --> REPEATER
    REPEATER --> HISTORY
    REPEATER -->|broadcast| CLIENT1
    REPEATER -->|broadcast| CLIENT2
    REPEATER -->|broadcast| MCP

    AGENT -- starts --> MCP
    
    style DAEMON fill:#e1f5fe
    style SOCKET fill:#f3e5f5
    style CLIENT1 fill:#fff2cc
    style CLIENT2 fill:#fff2cc
    style REPEATER fill:#e8f5e8
    style HISTORY fill:#f0f0f0
    style AGENT fill:#f0f0f0,stroke:#999,stroke-dasharray: 5 5

RepeaterActor Architecture Implemented

The daemon's core is built around a RepeaterActor that centralizes all message routing:

Key Components

  • RepeaterActor: Central message router that maintains subscriber list and message history
  • Client Identity System: Each client identifies itself with a descriptive identity on connection
  • Message History: In-memory buffer of recent messages for debugging and replay
  • Debug Interface: Commands to inspect message flow and client states

Message Flow

  1. Client Connection: Client connects to Unix socket and sends identity command
  2. Identity Registration: RepeaterActor records client identity and adds to subscriber list
  3. Message Broadcasting: All messages are broadcast to all connected clients
  4. History Tracking: Messages are stored in circular buffer for debugging

Client Identity System Implemented

Each client establishes an identity when connecting to help with debugging and monitoring:

Identity Format

Identities follow the pattern: prefix(pid:N,cwd:…/path)

  • prefix: Client type identifier
  • pid: Process ID for system correlation
  • cwd: Last two components of working directory

Client Types

Client TypeIdentity PrefixExample
MCP Servermcp-servermcp-server(pid:81332,cwd:…/symposium)
CLI Clientclient (default)client(pid:12345,cwd:…/my-project)
VSCode Extensionvscodevscode(pid:67890,cwd:…/workspace)
macOS Appappapp(pid:54321,cwd:…/directory)

Identity Commands

Clients send identity commands on connection:

#identify:mcp-server(pid:81332,cwd:…/symposium)

The daemon uses these identities for:

  • Debug message attribution
  • Connection tracking
  • Process correlation

Message Targeting and Routing Implemented

Broadcast Model

The RepeaterActor uses a simple broadcast model:

  • All messages are sent to all connected clients
  • Clients perform their own filtering based on message content
  • No server-side routing logic needed

Client-Side Filtering

Each client filters messages based on:

  • Message type relevance
  • Target working directory
  • Taskspace UUID matching

This approach keeps the daemon simple while allowing flexible client-side logic.

Debugging IPC Communications Implemented

The RepeaterActor architecture enables comprehensive debugging capabilities:

Debug Commands

# Show recent daemon messages
symposium-mcp debug dump-messages

# Show last 10 messages  
symposium-mcp debug dump-messages --count 10

# Output as JSON
symposium-mcp debug dump-messages --json

Debug Output Format

Recent daemon messages (3 of 15 total):
────────────────────────────────────────────────────────────────────────────────
[19:33:43.939] BROADCAST[mcp-server(pid:81332,cwd:…/symposium)] {"type":"taskspace_state",...}
[19:33:44.001] BROADCAST[vscode(pid:12345,cwd:…/my-project)] {"type":"register_taskspace_window",...}
[19:33:44.301] BROADCAST[app(pid:67890,cwd:…/workspace)] {"type":"marco",...}

Message History

  • Capacity: 1000 messages (configurable)
  • Storage: In-memory circular buffer
  • Persistence: Lost on daemon restart
  • Access: Via debug commands only

Common Debugging Scenarios

Connection Issues: Check if clients are connecting and identifying properly

symposium-mcp debug dump-messages --count 5

Message Flow: Verify messages are being broadcast to all clients

symposium-mcp debug dump-messages --json | jq '.[] | .from_identifier'

Client Identity: Confirm clients are using correct identity prefixes

symposium-mcp debug dump-messages | grep BROADCAST

Implementation Details Implemented

Actor System

The daemon uses Tokio actors following Alice Ryhl's actor pattern:

  • RepeaterActor: Message routing and history
  • ClientActor: Individual client connections
  • StdioHandle: Stdin/stdout bridging

Channel Architecture

  • mpsc channels: For actor communication
  • Unbounded channels: For message broadcasting
  • Oneshot channels: For debug command responses

Error Handling

  • Connection failures: Automatic client cleanup
  • Message parsing errors: Logged but don't crash daemon
  • Actor panics: Isolated to individual actors

Performance Characteristics

  • Memory usage: O(message_history_size + active_clients)
  • CPU usage: O(active_clients) per message
  • Latency: Single-digit milliseconds for local Unix sockets

Socket Management Implemented

Socket Location

Default: /tmp/symposium-daemon.sock Custom: /tmp/{prefix}-daemon.sock

Auto-Start Behavior

Clients can auto-start the daemon if not running:

symposium-mcp client --auto-start

Cleanup

  • Socket files removed on daemon shutdown
  • Stale sockets cleaned up on startup
  • Process termination handled gracefully

Testing Implemented

The RepeaterActor has comprehensive unit tests covering:

  • Message routing: Broadcast to all subscribers
  • Client management: Add/remove subscribers
  • Identity tracking: Client identifier handling
  • Message history: Circular buffer behavior
  • Debug commands: History retrieval and formatting

Tests use in-memory channels and mock clients for fast, reliable testing.

IPC Message type reference

For each message type that is sent in the record we record

  • purpose
  • expected payload (or "varies")
  • expected response (if any)
  • sent by (extension, MCP server, symposium app, etc)
  • any other relevant details

response

Sent by: All components (VSCode extension, MCP server, Symposium app)

Purpose: Acknowledge and respond to incoming requests

Payload: Varies based on the original message type

Notes: Response messages are special. They are sent in response to other messages and their fields are determined in response to that message type:

  • the id is equal to the id of the message being responding to
  • the payload type depends on the message being responded to

marco

Sent by: VSCode extension

Purpose: Discovery broadcast to find active MCP servers ("who's out there?")

Payload: {} (empty object)

Expected response: polo messages from active MCP servers

Notes: Uses simplified sender format (no full MessageSender object)

polo

Sent by: MCP server

Purpose: Response to marco discovery messages

Payload:

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct PoloPayload {
    // Shell PID is now at top level in IPCMessage
}

Expected response: None (broadcast response)

Notes: Server identification comes from the sender field in the IPCMessage

store_reference

Sent by: VSCode extension

Purpose: Store code references for later expansion by agents

Payload:

/// Payload for store_reference messages - generic key-value storage
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct StoreReferencePayload {
    /// UUID key for the reference
    pub key: String,
    /// Arbitrary JSON value - self-documenting structure determined by extension
    pub value: serde_json::Value,
}

Expected response: response with success confirmation

Target: MCP server

get_taskspace_state

Sent by: VSCode extension

Purpose: Query the current state of a taskspace

Payload:

interface TaskspaceRollCallPayload {
    taskspace_uuid: string;
}

Expected response: response with TaskspaceStateResponse

Target: Symposium app

register_taskspace_window

Sent by: VSCode extension

Purpose: Register a VSCode window with a specific taskspace

Payload:

interface RegisterTaskspaceWindowPayload {
    window_title: string;
    taskspace_uuid: string;
}

Expected response: response with success confirmation

Target: Symposium app

present_walkthrough

Sent by: MCP server

Purpose: Display interactive code walkthrough in VSCode

Payload:

#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)]
pub struct PresentWalkthroughParams {
    /// Markdown content with embedded XML elements (comment, gitdiff, action, mermaid)
    /// See dialectic guidance for XML element syntax and usage
    pub content: String,

    /// Base directory path for resolving relative file references
    #[serde(rename = "baseUri")]
    pub base_uri: String,
}

Expected response: None (display command)

Target: VSCode extension

log

Sent by: MCP server

Purpose: Send log messages to VSCode output channel

Payload:

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct LogParams {
    /// Log level
    pub level: LogLevel,

    /// Log message content
    pub message: String,
}

Expected response: None (logging command)

Target: VSCode extension

get_selection

Sent by: MCP server

Purpose: Request currently selected text from VSCode editor

Payload: {} (empty object)

Expected response: response with selected text or null

Target: VSCode extension

reload_window

Sent by: Daemon (on shutdown)

Purpose: Instruct all VSCode extensions to reload their windows

Payload: {} (empty object)

Expected response: None (command)

Target: All connected VSCode extensions

Notes: Broadcast message with generic sender (/tmp working directory)

goodbye

Sent by: MCP server

Purpose: Notify that the MCP server is shutting down

Payload: {} (empty object)

Expected response: None (notification)

Target: VSCode extension

resolve_symbol_by_name

Sent by: MCP server

Purpose: Find symbol definitions by name using LSP

Payload:

{
    symbol_name: string;
}

Expected response: response with Vec<ResolvedSymbol>

Target: VSCode extension

find_all_references

Sent by: MCP server

Purpose: Find all references to a symbol using LSP

Payload:

{
    symbol_name: string;
}

Expected response: response with Vec<FileLocation>

Target: VSCode extension

create_synthetic_pr

Sent by: MCP server

Purpose: Create a new synthetic pull request in VSCode

Payload: Synthetic PR creation data

Expected response: response with PR ID

Target: VSCode extension

update_synthetic_pr

Sent by: MCP server

Purpose: Update an existing synthetic pull request

Payload: Synthetic PR update data

Expected response: response with success confirmation

Target: VSCode extension

user_feedback

Sent by: VSCode extension

Purpose: Send user feedback (comments, review completion) to MCP server

Payload:

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct UserFeedbackPayload {
    pub review_id: String,
    pub feedback_type: String, // "comment" or "complete_review"
    pub file_path: Option<String>,
    pub line_number: Option<u32>,
    pub comment_text: Option<String>,
    pub completion_action: Option<String>, // "request_changes", "checkpoint", "return"
    pub additional_notes: Option<String>,
    pub context_lines: Option<Vec<String>>,
}

Expected response: response with acknowledgment

Target: MCP server

spawn_taskspace

Sent by: MCP server

Purpose: Request creation of a new taskspace

Payload:

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct SpawnTaskspacePayload {
    pub project_path: String,
    pub taskspace_uuid: String,
    pub name: String,
    pub task_description: String,
    pub initial_prompt: String,
}

Expected response: response with taskspace info

Target: Symposium app

log_progress

Sent by: MCP server

Purpose: Report progress with visual indicators

Payload:

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct LogProgressPayload {
    pub project_path: String,
    pub taskspace_uuid: String,
    pub message: String,
    pub category: ProgressCategory,
}

Expected response: None (display command)

Target: Symposium app

signal_user

Sent by: MCP server

Purpose: Request user attention for assistance

Payload:

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct SignalUserPayload {
    pub project_path: String,
    pub taskspace_uuid: String,
    pub message: String,
}

Expected response: None (notification)

Target: Symposium app

update_taskspace

Sent by: MCP server

Purpose: Update taskspace name and description

Payload:

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct UpdateTaskspacePayload {
    pub project_path: String,
    pub taskspace_uuid: String,
    pub name: String,
    pub description: String,
}

Expected response: response with success confirmation

Target: Symposium app

delete_taskspace

Preview: taskspace-deletion-dialog-confirmation

Sent by: MCP server

Purpose: Request deletion of the current taskspace with user confirmation

Payload:

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct DeleteTaskspacePayload {
    pub project_path: String,
    pub taskspace_uuid: String,
}

Expected response: response with success confirmation (sent after user confirms) or error (if user cancels)

Target: Symposium app

Notes: This message triggers a confirmation dialog. The response is deferred until the user either confirms or cancels the deletion. If confirmed, the taskspace is deleted and a success response is sent. If cancelled, an error response is sent with the message "Taskspace deletion was cancelled by user".

taskspace_roll_call

Sent by: Symposium app

Purpose: Broadcast to discover active taskspaces for window registration

Payload: {} (empty object)

Expected response: Taskspace registration responses

Target: All components (broadcast)

Message Routing

Messages are routed based on sender information:

  • Directory matching: Messages are delivered to extensions whose workspace contains the sender's working directory
  • PID matching: When shellPid is provided, messages are delivered to extensions that have a terminal with that PID
  • Taskspace routing: Messages with taskspaceUuid can be routed to specific taskspace-aware components

Core IPC Types

The IPC message format is consistent across all components:

IPCMessage Structure

Rust (MCP Server):

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct IPCMessage {
    /// Message type identifier
    #[serde(rename = "type")]
    pub message_type: IPCMessageType,

    /// Unique message ID for response tracking
    pub id: String,

    /// Sender information for routing
    pub sender: MessageSender,

    /// Message payload - for store_reference: { key: string, value: arbitrary_json }
    pub payload: serde_json::Value,
}

TypeScript (VSCode Extension):

interface IPCMessage {
    type: string;
    id: string;
    sender: MessageSender;
    payload: any;
}

Swift (Symposium App): Note: Swift implementation exists but not currently documented with anchors.

MessageSender Structure

Rust (MCP Server):

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct MessageSender {
    /// Working directory - always present for reliable matching
    #[serde(rename = "workingDirectory")]
    pub working_directory: String,

    /// Optional taskspace UUID for taskspace-specific routing
    #[serde(rename = "taskspaceUuid")]
    pub taskspace_uuid: Option<String>,

    /// Optional shell PID - only when VSCode parent found
    #[serde(rename = "shellPid")]
    pub shell_pid: Option<u32>,
}

TypeScript (VSCode Extension):

interface MessageSender {
    workingDirectory: string;      // Always present - reliable matching
    taskspaceUuid?: string;        // Optional - for taskspace-specific routing
    shellPid?: number;             // Optional - only when VSCode parent found
}

Swift (Symposium App): Note: Swift implementation exists but not currently documented with anchors.

Symposium MCP server + IDE extension specifics

MCP Server Actor Architecture

Completed RFDs: ipc-actor-refactoring

Internal architecture for the MCP server's actor-based IPC system

Overview

The MCP server uses a focused actor architecture following Alice Ryhl's Tokio actor pattern to handle IPC communication. This replaces the previous monolithic IPCCommunicator with specialized actors that communicate via message passing channels.

Actor Responsibilities

Dispatch Actor

Purpose: Message routing and reply correlation
Location: src/actor/dispatch.rs

  • Routes incoming IPCMessages to appropriate handlers
  • Tracks pending replies with timeout management
  • Correlates responses with waiting callers
  • Eliminates shared mutable state through message passing
#![allow(unused)]
fn main() {
// Core message types
enum DispatchRequest {
    SendMessage { message: IPCMessage, reply_tx: Option<oneshot::Sender<serde_json::Value>> },
    CancelReply { id: String },
}
}

Client Actor

Purpose: Transport layer for daemon communication
Location: src/actor/client.rs (planned)

  • Manages Unix socket connections with retry logic
  • Auto-starts daemon process when needed
  • Serializes/deserializes messages to/from IPCMessage
  • Forwards parsed messages via tokio channels

Message Flow:

Unix Socket → ClientActor → parse → tokio::channel → DispatchActor
DispatchActor → tokio::channel → ClientActor → serialize → Unix Socket

Stdout Actor

Purpose: CLI output for daemon client mode
Location: src/actor/stdout.rs (planned)

  • Receives IPCMessages from client actor
  • Serializes messages back to JSON
  • Prints to stdout for CLI consumption

Usage: When daemon::run_client() is called, it wires ClientActorStdoutActor instead of ClientActorDispatchActor.

Dispatch Actor

Purpose: Message routing and Marco/Polo discovery
Location: src/actor/dispatch.rs

  • Routes messages to appropriate handlers based on type
  • Handles Marco/Polo discovery protocol inline
  • Manages message bus coordination
  • Responds to marco messages with polo

Reference Actor

Purpose: Code reference storage and retrieval
Location: src/actor/reference.rs

  • Stores code references for later retrieval
  • Manages reference lifecycle
  • Provides lookup capabilities

Architecture Patterns

Actor Structure

Each actor follows the standard Tokio pattern:

#![allow(unused)]
fn main() {
// Message enum defining operations
enum ActorRequest {
    DoSomething { data: String, reply_tx: oneshot::Sender<Result> },
}

// Actor struct owning state and message receiver
struct Actor {
    receiver: mpsc::Receiver<ActorRequest>,
    // actor-specific state
}

// Handle providing public API
#[derive(Clone)]
struct ActorHandle {
    sender: mpsc::Sender<ActorRequest>,
}
}

Channel-Based Communication

Actors communicate exclusively through typed channels:

  • mpsc channels: For actor request/response patterns
  • oneshot channels: For reply correlation
  • broadcast channels: For pub/sub patterns (if needed)

Error Handling

  • Each actor handles its own errors internally
  • Failures are communicated through result types in messages
  • Actors can restart independently without affecting others

Integration Points

MCP Server Mode

VSCode Extension ↔ Unix Socket ↔ ClientActor ↔ DispatchActor ↔ MCP Handlers

CLI Daemon Mode

stdin → daemon::run_client → ClientActor ↔ StdoutActor → stdout

IPCCommunicator Compatibility

The existing IPCCommunicator becomes a thin wrapper:

#![allow(unused)]
fn main() {
impl IPCCommunicator {
    // Delegates to ClientActor + DispatchActor handles
    pub async fn send_message(&self, msg: IPCMessage) -> Result<serde_json::Value> {
        self.dispatch_handle.send_message_with_reply(msg).await
    }
}
}

Testing Strategy

Unit Testing

  • Each actor can be tested in isolation
  • Mock channels for testing message flows
  • Timeout and error scenarios easily testable

Integration Testing

  • Wire actors together in test configurations
  • Test complete message flows end-to-end
  • Verify proper cleanup and shutdown

Migration Path

  1. Phase 1 ✅: Extract dispatch actor from ipc.rs
  2. Phase 2: Implement client actor and stdout actor
  3. Phase 3: Refactor daemon::run_client to use actors
  4. Phase 4: Update IPCCommunicator to use actor handles
  5. Phase 5: Remove legacy code and add comprehensive tests

Benefits

  • Testability: Each actor can be unit tested independently
  • Maintainability: Clear separation of concerns and responsibilities
  • Reliability: Message passing eliminates race conditions and lock contention
  • Flexibility: Easy to add new actors or modify message flows
  • Performance: Async message passing scales better than shared mutable state

Future Considerations

  • Server Actor: May be needed if we handle incoming connections differently
  • Metrics Actor: Could collect performance and health metrics
  • Configuration Actor: Could manage dynamic configuration updates
  • Supervision: Consider actor supervision patterns for fault tolerance

Guidance and Initialization

This chapter describes how Symposium provides agents with collaboration patterns, project context, and initialization instructions through embedded guidance and streamlined boot sequences.

Problem Statement

Current Boot Sequence Issues

The current taskspace initialization has several problems:

  1. Shell Argument Length Limits: Long initial prompts passed via command line arguments get truncated, creating messy output with shell continuation prompts (dquote>).

  2. Manual Context Configuration: Agents must manually discover and read project documentation (AGENTS.md, CLAUDE.md, etc.) to understand collaboration patterns.

  3. Fragmented Setup: Users need to configure both user-wide context files AND install MCP servers separately, with potential sync issues.

  4. Awkward User Experience: The terminal shows truncated commands and confusing output during agent startup.

Example of Current Problems

When launching a taskspace, users see:

q chat "Hi, welcome\! You are a new agent just getting started as part of the project Symposium..."
# Command gets truncated, showing:
dquote> User's task description:
dquote> This workspace is just for testing — I'm planning to delete it...

Design Solution

Stored Prompt + MCP Resources Approach

Instead of passing long prompts as command-line arguments, we use a stored prompt system combined with MCP resources:

# Clean, simple command
q chat /yiasou

Where /yiasou is a stored prompt that instructs the agent to load specific MCP resources with behavioral directives.

MCP Resources Architecture

The MCP server exposes guidance files as individual resources using the standard MCP resource protocol:

#![allow(unused)]
fn main() {
// Expose guidance files as MCP resources
async fn list_resources(&self, ...) -> Result<ListResourcesResult, McpError> {
    Ok(ListResourcesResult {
        resources: vec![
            Resource {
                uri: "main.md".into(),
                name: Some("Collaboration Patterns".into()),
                description: Some("Mindful collaboration patterns demonstrated through dialogue".into()),
                mime_type: Some("text/markdown".into()),
            },
            Resource {
                uri: "walkthrough-format.md".into(),
                name: Some("Walkthrough Format".into()),
                description: Some("Specification for creating interactive code walkthroughs".into()),
                mime_type: Some("text/markdown".into()),
            },
            Resource {
                uri: "coding-guidelines.md".into(),
                name: Some("Coding Guidelines".into()),
                description: Some("Development best practices and standards".into()),
                mime_type: Some("text/markdown".into()),
            },
        ],
    })
}
}

Complete Boot Sequence

The /yiasou stored prompt provides a structured initialization with resource loading instructions:

# Agent Boot Sequence

This prompt defines the agent boot sequence.

If you encounter ambiguous instructions, remember to ask questions and seek 
clarifications before proceeding, particularly with side-effect-ful or 
dangerous actions (e.g., deleting content or interacting with remote systems).

## Load Collaboration Patterns

Load the resource `main.md` into your working context. This contains collaboration 
patterns demonstrated through dialogue. Embody the collaborative spirit shown in 
these examples - approach our work with genuine curiosity, ask questions when 
something isn't clear, and trust that we'll navigate together what's worth pursuing.

## Load Walkthrough Format

Load the resource `walkthrough-format.md` into your working context. This defines 
how to create interactive code walkthroughs using markdown with embedded XML 
elements for comments, diffs, and actions.

## Load Coding Guidelines  

Load the resource `coding-guidelines.md` into your working context. Follow these 
development standards and best practices in all code work.

## Initial Task

<dynamic content fetched via MCP tool/IPC>

Implementation Architecture

File Organization

symposium/mcp-server/
├── src/
│   ├── guidance/           # Guidance files exposed as MCP resources
│   │   ├── main.md        # Collaboration patterns → @../../../symposium/mcp-server/src/guidance/main.md
│   │   ├── walkthrough-format.md → @../../../symposium/mcp-server/src/guidance/walkthrough-format.md
│   │   └── coding-guidelines.md → @../../../symposium/mcp-server/src/guidance/coding-guidelines.md
│   ├── server.rs          # MCP server with resource + prompt support → @../../../symposium/mcp-server/src/server.rs
│   ├── types.rs           # IPC message types and payloads → @../../../symposium/mcp-server/src/types.rs
│   └── ipc.rs             # IPC communication layer → @../../../symposium/mcp-server/src/ipc.rs

Key Implementation Files

MCP Server Core → @../../../symposium/mcp-server/src/server.rs

  • list_resources() - Exposes guidance files as MCP resources
  • read_resource() - Serves guidance file content
  • get_prompt() - Implements /yiasou stored prompt
  • assemble_yiasou_prompt() - Dynamic prompt assembly with taskspace context
  • expand_reference() - Enhanced tool supporting guidance files and yiasou reference

IPC Communication → @../../../symposium/mcp-server/src/ipc.rs

  • get_taskspace_state() - Fetches real taskspace context from daemon/app
  • Message routing and error handling for dynamic context integration

Type Definitions → @../../../symposium/mcp-server/src/types.rs

  • GetTaskspaceStatePayload - IPC request structure
  • TaskspaceStateResponse - Taskspace context response
  • IPCMessageType::GetTaskspaceState - Message type for context fetching

Embedded Guidance Files

  • Collaboration Patterns → @../../../symposium/mcp-server/src/guidance/main.md
  • Walkthrough Format → @../../../symposium/mcp-server/src/guidance/walkthrough-format.md
  • Coding Guidelines → @../../../symposium/mcp-server/src/guidance/coding-guidelines.md

Data Flow

sequenceDiagram
    participant User
    participant Ext as VSCode Extension
    participant Q as Q CLI
    participant MCP as MCP Server
    participant Daemon
    participant Symposium as Symposium App

    User->>Ext: VSCode opens taskspace
    Ext->>Q: Launch "q chat /yiasou"
    Q->>MCP: Request prompt "/yiasou"
    MCP->>Daemon: IPC: get_taskspace_state(uuid)
    Daemon->>Symposium: Forward: get_taskspace_state
    Symposium-->>Daemon: Return task description, shouldLaunch, etc.
    Daemon-->>MCP: Forward taskspace context
    MCP->>MCP: Assemble /yiasou prompt with resource loading instructions
    MCP-->>Q: Return prompt with "Load resource main.md", etc.
    Q->>Q: Start agent conversation
    Q->>MCP: Agent loads resource "main.md"
    MCP-->>Q: Return collaboration patterns content
    Q->>MCP: Agent loads resource "walkthrough-format.md"
    MCP-->>Q: Return walkthrough format content
    Q->>MCP: Agent loads resource "coding-guidelines.md"
    MCP-->>Q: Return coding guidelines content

Architectural Benefits

This design provides several advantages over embedded content:

  1. Modularity: Each guidance file is independently accessible
  2. Selective Loading: Agents can load only relevant guidance
  3. Resource Introspection: Standard MCP resource listing shows available guidance
  4. Behavioral Directives: Each resource comes with specific usage instructions
  5. Debugging: Easy to see which guidance files are being loaded and used

Implementation Clarifications

MCP Resource Support: The Rust MCP SDK fully supports resources through list_resources() and read_resource() methods in the ServerHandler trait.

MCP Prompt Support: The Rust MCP SDK fully supports dynamic prompts through get_prompt() method, which can perform async computation and return dynamically assembled content.

Taskspace UUID Detection: Existing code in the MCP server already handles finding the taskspace UUID from the current working directory.

Error Handling: If the MCP server can't reach the daemon/app, /yiasou will omit the "Initial Task" section but still provide resource loading instructions.

Resource Loading Flow: The /yiasou prompt instructs the agent to load specific resources, and the agent makes separate MCP resource requests to fetch the actual content.

Dynamic Prompt Assembly: /yiasou will be implemented as an MCP prompt (not resource) that dynamically computes content in the get_prompt() method by making IPC calls for task context.

Migration Strategy: Changes are purely additive until the extension is updated - no backwards compatibility concerns during development.

Benefits

  1. Clean User Experience: Simple /yiasou command instead of truncated arguments
  2. Modular Guidance: Each guidance file is independently accessible as an MCP resource
  3. Selective Loading: Agents can choose which guidance to load based on context
  4. Behavioral Directives: Each resource comes with specific instructions for how to embody that guidance
  5. Standard Protocol: Uses standard MCP resource protocol for guidance access
  6. Automatic Updates: New MCP server versions include updated guidance
  7. No Manual Configuration: No separate context files to install or maintain
  8. Versioned Guidance: Collaboration patterns are versioned with the codebase

Implementation Plan

Phase 1: Embedded Guidance ✅ COMPLETE

  • Add rust-embed dependency to MCP server
  • Create guidance/ directory structure
  • Ask user to populate directory with collaboration patterns and other guidance
  • Implement guidance file loading in MCP server
  • Add comprehensive tests for guidance loading functionality
  • Create test tool to verify guidance assembly works correctly

Status: Phase 1 is complete and tested. All guidance files are embedded correctly and the assemble_yiasou_prompt() method successfully combines them into a complete initialization prompt.

Phase 2: MCP Resource System ✅ COMPLETE

  • Implement list_resources() method to expose guidance files as MCP resources
  • Implement read_resource() method to serve guidance file content
  • Test resource listing and reading through MCP protocol
  • Update guidance files to be optimized for individual loading

Status: Phase 2 is complete and tested. All guidance files are now exposed as MCP resources with proper metadata and can be loaded individually by agents.

Phase 3: MCP Prompt System ✅ COMPLETE

  • Implement /yiasou prompt using MCP server prompt capabilities
  • Create prompt assembly logic with resource loading instructions
  • Add behavioral directives for each resource type (using inviting language)
  • Test prompt delivery through MCP protocol

Phase 4: Dynamic Context Integration ✅ COMPLETE

  • Implement IPC call for taskspace context in /yiasou prompt
  • Add task description fetching
  • Integrate project-specific information
  • Test complete boot sequence with resource loading

Phase 5: Migration and Testing ✅ COMPLETE

  • Update Swift application to use unified TaskspaceState protocol
  • Implement unified IPC message handling in macOS app
  • Test end-to-end compilation and protocol compatibility
  • Document new TaskspaceState protocol in design docs
  • Update affected mdbook chapters:
    • Added comprehensive TaskspaceState protocol documentation
    • work-in-progress/mvp/taskspace-bootup-flow.md - Remove old flow references
    • design/startup-and-window-management.md - Update startup sequence
    • Any other chapters referencing current boot sequence
  • Remove old embedded guidance approach - replaced with MCP resources

Taskspace State Protocol

The taskspace state protocol enables dynamic agent initialization and taskspace management through a unified IPC message system. This protocol is used by the /yiasou prompt system to fetch real taskspace context and by the update_taskspace tool to modify taskspace properties.

Protocol Overview

Message Type: IPCMessageType::TaskspaceState

Request Structure: TaskspaceStateRequest

#![allow(unused)]
fn main() {
{
    project_path: String,        // Path to .symposium project
    taskspace_uuid: String,      // UUID of the taskspace
    name: Option<String>,        // None = don't update, Some(value) = set new name
    description: Option<String>, // None = don't update, Some(value) = set new description
}
}

Response Structure: TaskspaceStateResponse

#![allow(unused)]
fn main() {
{
    name: Option<String>,         // Current taskspace name (user-visible)
    description: Option<String>,  // Current taskspace description (user-visible)
    initial_prompt: Option<String>, // LLM task description (cleared after updates)
}
}

Field Semantics

Request Fields:

  • project_path: Absolute path to the .symposium project directory
  • taskspace_uuid: Unique identifier for the taskspace (extracted from directory structure)
  • name: Optional new name to set (None = read-only operation)
  • description: Optional new description to set (None = read-only operation)

Response Fields:

  • name: User-visible taskspace name displayed in GUI tabs, window titles, etc.
  • description: User-visible summary shown in GUI tooltips, status bars, etc.
  • initial_prompt: Task description provided to LLM during agent initialization

Operation Types

Read Operation (Get Taskspace State)

#![allow(unused)]
fn main() {
TaskspaceStateRequest {
    project_path: "/path/to/project.symposium".to_string(),
    taskspace_uuid: "task-abc123...".to_string(),
    name: None,        // Don't update name
    description: None, // Don't update description
}
}

Used by:

  • /yiasou prompt assembly to fetch taskspace context
  • Agent initialization to get current state

Write Operation (Update Taskspace)

#![allow(unused)]
fn main() {
TaskspaceStateRequest {
    project_path: "/path/to/project.symposium".to_string(),
    taskspace_uuid: "task-abc123...".to_string(),
    name: Some("New Taskspace Name".to_string()),
    description: Some("Updated description".to_string()),
}
}

Used by:

  • update_taskspace MCP tool when agent modifies taskspace properties
  • GUI application when user changes taskspace settings

Lifecycle Management

The protocol implements automatic initial_prompt cleanup:

  1. Agent Initialization:

    • Agent requests taskspace state (read operation)
    • Receives initial_prompt with task description
    • Uses prompt content for initialization context
  2. Agent Updates Taskspace:

    • Agent calls update_taskspace tool (write operation)
    • GUI application processes the update
    • GUI automatically clears initial_prompt field
    • Returns updated state with initial_prompt: None
  3. Natural Cleanup:

    • Initial prompt is only available during first agent startup
    • Subsequent operations don't include stale initialization data
    • No manual cleanup required

Implementation Details

MCP Server Methods:

  • get_taskspace_state() → @../../../symposium/mcp-server/src/ipc.rs (read operation)
  • update_taskspace() → @../../../symposium/mcp-server/src/ipc.rs (write operation)

Message Flow:

Agent → MCP Server → Daemon → GUI Application
                              ↓
Agent ← MCP Server ← Daemon ← GUI Application

Error Handling:

  • Taskspace detection failure → extract_project_info() error
  • Daemon unreachable → IPC timeout/connection error
  • GUI application unavailable → Empty response or error
  • Graceful degradation in /yiasou prompt assembly

Benefits

Unified Protocol:

  • Single message type for all taskspace state operations
  • Consistent request/response pattern
  • Reduced protocol complexity

Automatic Lifecycle Management:

  • Natural initial_prompt cleanup on updates
  • No manual state management required
  • Clear separation between initialization and runtime data

Dynamic Agent Initialization:

  • Real taskspace context in /yiasou prompts
  • Context-aware agent startup
  • Seamless integration with MCP resource system

Usage Examples

Agent Initialization (via /yiasou prompt):

#![allow(unused)]
fn main() {
// MCP server calls during prompt assembly
let state = ipc.get_taskspace_state().await?;
// Returns: { name: "Feature X", description: "Add new API", initial_prompt: "Implement REST endpoint..." }
}

Agent Updates Taskspace:

#![allow(unused)]
fn main() {
// Agent calls update_taskspace tool
let updated_state = ipc.update_taskspace("Feature X v2", "Updated API design").await?;
// Returns: { name: "Feature X v2", description: "Updated API design", initial_prompt: None }
}

This protocol enables the complete dynamic agent initialization system while maintaining clean separation between user-visible properties and LLM-specific initialization data.

Future Enhancements

Customizable Guidance

  • Support for project-specific guidance overrides
  • User-level customization of collaboration patterns
  • Team-specific guidance variations

Advanced Context

  • Integration with project documentation systems
  • Automatic detection of project type and relevant patterns
  • Context-aware guidance based on task type

Performance Optimization

  • Lazy loading of guidance files
  • Caching of assembled prompts
  • Compression of embedded resources

MCP Server Overview

The Symposium MCP server (symposium-mcp) provides a comprehensive set of tools for AI assistants to interact with VSCode and coordinate taskspace orchestration.

Tool Categories

Agent Initialization System

The MCP server provides the foundation for dynamic agent initialization through the yiasou prompt system and embedded guidance resources. For complete details, see Guidance and Initialization.

Key capabilities:

Architecture

The MCP server operates as a bridge between AI assistants and the VSCode extension:

  1. Process Discovery: Automatically discovers the parent VSCode process
  2. IPC Communication: Connects to the daemon message bus via Unix socket
  3. Tool Execution: Processes MCP tool calls and routes them appropriately
  4. Resource Serving: Provides embedded guidance files as MCP resources
  5. Dynamic Prompts: Assembles context-aware initialization prompts
  6. Response Handling: Returns structured results to the AI assistant

Configuration

The server is configured through your AI assistant's MCP settings:

{
  "mcpServers": {
    "symposium": {
      "command": "/path/to/symposium-mcp",
      "args": ["server"]
    }
  }
}

Error Handling

All tools include comprehensive error handling:

  • IPC Failures: Graceful degradation when VSCode connection is lost
  • Invalid Parameters: Clear error messages for malformed requests
  • Process Discovery: Fallback mechanisms for PID detection
  • Resource Loading: Fallback to basic prompts when guidance unavailable
  • Context Fetching: Yiasou prompt works even without taskspace context
  • Test Mode: Mock responses when DIALECTIC_TEST_MODE=1 is set

IDE Integration Tools

get_selection

#![allow(unused)]
fn main() {
// --- Tool definition ------------------
    #[tool(
        description = "\
            Get the currently selected text from any active editor in VSCode.\n\
            Works with source files, review panels, and any other text editor.\n\
            Returns null if no text is selected or no active editor is found.\
        "
    )]
    async fn get_selection(&self) -> Result<CallToolResult, McpError> {
}

Returns: { selectedText: string | null }
Use case: Retrieve user-selected code for analysis or modification

ide_operation

#![allow(unused)]
fn main() {
// --- Parameters -----------------------
#[derive(Debug, Deserialize, Serialize, schemars::JsonSchema)]
struct IdeOperationParams {
    /// Dialect program to execute
    program: String,
}

// --- Tool definition ------------------
    #[tool(
        description = "\
            Execute IDE operations using a structured JSON mini-language.\n\
            This tool provides access to VSCode's Language Server Protocol (LSP) capabilities\n\
            through a composable function system.\n\
            \n\
            Common operations:\n\
            - findDefinitions(\"MyFunction\") or findDefinition(\"MyFunction\") - list of locations where a symbol named `MyFunction` is defined\n\
            - findReferences(\"MyFunction\") - list of locations where a symbol named `MyFunction` is referenced\n\
            \n\
            To find full guidelines for usage, use the `expand_reference` with `walkthrough-format.md`.\n\
            "
    )]
    async fn ide_operation(
        &self,
        Parameters(params): Parameters<IdeOperationParams>,
    ) -> Result<CallToolResult, McpError> {
}

Common Dialect functions:

  • findDefinitions("symbol") - Find where a symbol is defined
  • findReferences("symbol") - Find all uses of a symbol
  • search("file.rs", "pattern") - Search file for regex pattern
  • search("dir", "pattern", ".rs") - Search directory for pattern in specific file types

Use case: Navigate code structure, find definitions, search for patterns

Code Walkthrough Tools

present_walkthrough

#![allow(unused)]
fn main() {
// --- Parameters -----------------------
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)]
pub struct PresentWalkthroughParams {
    /// Markdown content with embedded XML elements (comment, gitdiff, action, mermaid)
    /// See dialectic guidance for XML element syntax and usage
    pub content: String,

    /// Base directory path for resolving relative file references
    #[serde(rename = "baseUri")]
    pub base_uri: String,
}

// --- Tool definition ------------------
    #[tool(
        description = "\
            Display a code walkthrough in the user's IDE.\n\
            Use this when the user\n\
            (1) requests a walkthrough or that you walk through code or\n\
            (2) asks that you explain how code works.\n\
            \n\
            Accepts markdown content with special code blocks.\n\
            \n\
            To find full guidelines for usage, use the `expand_reference` with `walkthrough-format.md`.\n\
            \n\
            Quick tips:\n\
            \n\
            Display a mermaid graph:\n\
            ```mermaid\n\
            (Mermaid content goes here)\n\
            ```\n\
            \n\
            Add a comment to a particular line of code:\n\
            ```comment\n\
            location: findDefinition(`symbol_name`)\n\
            \n\
            (Explanatory text goes here)\n\
            ```\n\
            \n\
            Add buttons that will let the user send you a message:\n\
            ```action\n\
            button: (what the user sees)\n\
            \n\
            (what message you will get)\n\
            ```\n\
        "
    )]
    async fn present_walkthrough(
        &self,
        Parameters(params): Parameters<PresentWalkthroughParams>,
    ) -> Result<CallToolResult, McpError> {
}

Supported XML elements:

  • <comment location="EXPR" icon="ICON">content</comment> - Code comments at specific locations
  • <action button="TEXT">message</action> - Interactive buttons
  • <mermaid>diagram</mermaid> - Architecture diagrams

Use case: Create interactive code tours and explanations

Synthetic Pull Request Tools Planned

request_review Planned

Implementation pending - will generate structured code reviews from commits.

Use case: Generate structured code reviews from commits

update_review Planned

Implementation pending - will update existing code reviews.

// --- Tool definition ------------------


**Use case**: Manage review workflows and collect user feedback

## `get_review_status`

```rust
// --- Tool definition ------------------

Use case: Check review state and progress

Taskspace Orchestration Tools

spawn_taskspace

#![allow(unused)]
fn main() {
// --- Parameters -----------------------
#[derive(Debug, Deserialize, Serialize, schemars::JsonSchema)]
struct SpawnTaskspaceParams {
    /// Name for the new taskspace
    name: String,
    /// Description of the task to be performed
    task_description: String,
    /// Initial prompt to provide to the agent when it starts
    initial_prompt: String,
}

// --- Tool definition ------------------
    #[tool(
        description = "Create a new taskspace with name, description, and initial prompt. \
                       The new taskspace will be launched with VSCode and the configured agent tool."
    )]
    async fn spawn_taskspace(
        &self,
        Parameters(params): Parameters<SpawnTaskspaceParams>,
    ) -> Result<CallToolResult, McpError> {
}

Use case: Create new collaborative workspaces for specific tasks

update_taskspace

#![allow(unused)]
fn main() {
// --- Parameters -----------------------
#[derive(Debug, Deserialize, Serialize, schemars::JsonSchema)]
struct UpdateTaskspaceParams {
    /// New name for the taskspace
    name: String,
    /// New description for the taskspace
    description: String,
}

// --- Tool definition ------------------
    #[tool(
        description = "Update the name and description of the current taskspace. \
                       Use this to set meaningful names and descriptions based on user interaction."
    )]
    async fn update_taskspace(
        &self,
        Parameters(params): Parameters<UpdateTaskspaceParams>,
    ) -> Result<CallToolResult, McpError> {
}

Use case: Update taskspace name and description based on user interaction

delete_taskspace

Use case: Delete the current taskspace, removing filesystem directories, closing VSCode windows, and cleaning up git worktrees

log_progress

#![allow(unused)]
fn main() {
// --- Parameters -----------------------
#[derive(Debug, Deserialize, Serialize, schemars::JsonSchema)]
struct LogProgressParams {
    /// Progress message to display
    message: String,
    /// Category for visual indicator (info, warn, error, milestone, question)
    category: String,
}

// --- Tool definition ------------------
    #[tool(description = "Report progress with visual indicators. \
                       Categories: 'info' or ℹ️, 'warn' or ⚠️, 'error' or ❌, 'milestone' or ✅, 'question' or ❓")]
    async fn log_progress(
        &self,
        Parameters(params): Parameters<LogProgressParams>,
    ) -> Result<CallToolResult, McpError> {
}

Progress categories:

#![allow(unused)]
fn main() {
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum ProgressCategory {
    Info,
    Warn,
    Error,
    Milestone,
    Question,
}
}

Use case: Keep users informed of agent progress and status

signal_user

#![allow(unused)]
fn main() {
// --- Parameters -----------------------
#[derive(Debug, Deserialize, Serialize, schemars::JsonSchema)]
struct SignalUserParams {
    /// Message describing why user attention is needed
    message: String,
}

// --- Tool definition ------------------
    #[tool(description = "Request user attention for assistance. \
                       The taskspace will be highlighted and moved toward the front of the panel.")]
    async fn signal_user(
        &self,
        Parameters(params): Parameters<SignalUserParams>,
    ) -> Result<CallToolResult, McpError> {
}

Use case: Alert users when agents need help or input

Reference System Tools

expand_reference

#![allow(unused)]
fn main() {
// --- Parameters -----------------------
#[derive(Debug, Serialize, Deserialize, schemars::JsonSchema)]
pub struct ExpandReferenceParams {
    /// The reference ID to expand
    pub id: String,
}

// --- Tool definition ------------------
    #[tool(description = "
        Expand a compact reference (denoted as `<symposium-ref id='..'/>`) to get full context. \
        Invoke with the contents of `id` attribute. Returns structured JSON with all available context data. \
    ")]
    async fn expand_reference(
        &self,
        Parameters(params): Parameters<ExpandReferenceParams>,
    ) -> Result<CallToolResult, McpError> {
}

Use case: Retrieve stored context for compact references. Also retrieves the bootup prompt ("yiasou") and the various guidance files that are embedded (e.g., "main.md").

Rust Development Tools

The Rust development tools help agents work with Rust crates by providing access to source code, examples, and documentation.

get_rust_crate_source

Preview: rust-crate-sources-tool

Purpose: Extract and optionally search Rust crate source code from crates.io

Parameters:

  • crate_name (required): Name of the crate (e.g., "tokio")
  • version (optional): Semver range (e.g., "1.0", "^1.2", "~1.2.3")
  • pattern (optional): Regex pattern for searching within sources

Behavior:

  • Without pattern: Extracts crate source and returns path information
  • With pattern: Extracts crate source AND performs pattern search, returning matches

Version Resolution:

  1. If version specified: Uses semver range to find latest matching version
  2. If no version: Checks current project's lockfile for the crate version
  3. If not in project: Uses latest version from crates.io

Response Format:

Without pattern (extraction only):

{
  "crate_name": "tokio",
  "version": "1.35.0",
  "checkout_path": "/path/to/extracted/crate",
  "message": "Crate tokio v1.35.0 extracted to /path/to/extracted/crate"
}

With pattern (extraction + search):

{
  "crate_name": "tokio",
  "version": "1.35.0", 
  "checkout_path": "/path/to/extracted/crate",
  "example_matches": [
    {
      "file_path": "examples/hello_world.rs",
      "line_number": 8,
      "context_start_line": 6,
      "context_end_line": 10,
      "context": "#[tokio::main]\nasync fn main() {\n    tokio::spawn(async {\n        println!(\"Hello from spawn!\");\n    });"
    }
  ],
  "other_matches": [
    {
      "file_path": "src/task/spawn.rs", 
      "line_number": 156,
      "context_start_line": 154,
      "context_end_line": 158,
      "context": "/// Spawns a new asynchronous task\n///\npub fn spawn<T>(future: T) -> JoinHandle<T::Output>\nwhere\n    T: Future + Send + 'static,"
    }
  ],
  "message": "Crate tokio v1.35.0 extracted to /path/to/extracted/crate"
}

Key Features:

  • Caching: Extracted crates are cached to avoid redundant downloads
  • Project Integration: Automatically detects versions from current Rust project
  • Example Priority: Search results separate examples from other source files
  • Context Preservation: Includes surrounding code lines for better understanding

Common Usage Patterns:

  1. Explore API: get_rust_crate_source(crate_name: "serde") - Get crate structure
  2. Find Examples: get_rust_crate_source(crate_name: "tokio", pattern: "spawn") - Search for usage patterns
  3. Version-Specific: get_rust_crate_source(crate_name: "clap", version: "^4.0", pattern: "derive") - Target specific versions

This tool enables agents to provide accurate, example-driven assistance for Rust development by accessing real crate source code rather than relying on potentially outdated training data.

The Symposium Reference System

The symposium reference system is a generic key-value store that allows VSCode extensions to share arbitrary context data with AI assistants.

Core Concept

Extensions create compact references like <symposium-ref id="uuid"/> and store arbitrary JSON context. AI agents expand these references to get the JSON and interpret it contextually based on its self-documenting structure.

Key insight: There is no fixed schema. The system stores (uuid, arbitrary_json_value) pairs where the JSON structure is determined by the extension and interpreted by the receiving agent.

Message Format

The payload structure for store_reference messages:

TypeScript (Extension):

interface StoreReferencePayload {
    /** UUID key for the reference */
    key: string;
    /** Arbitrary JSON value - self-documenting structure determined by extension */
    value: any;
}

Rust (MCP Server):

#![allow(unused)]
fn main() {
/// Payload for store_reference messages - generic key-value storage
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct StoreReferencePayload {
    /// UUID key for the reference
    pub key: String,
    /// Arbitrary JSON value - self-documenting structure determined by extension
    pub value: serde_json::Value,
}
}

Usage Examples

// Code selection context:
store_reference("uuid-1", {
  relativePath: "src/auth.ts",
  selectionRange: { start: {line: 10, column: 5}, end: {line: 15, column: 2} },
  selectedText: "function validateToken() { ... }"
});

// File reference context:
store_reference("uuid-2", {
  filePath: "README.md", 
  type: "documentation"
});

// Custom application context:
store_reference("uuid-3", {
  queryType: "database_schema",
  tableName: "users", 
  fields: ["id", "email", "created_at"]
});

Implementation

Storage: The MCP server stores references as HashMap<String, serde_json::Value> where the key is the UUID and the value is arbitrary JSON.

Retrieval: The expand_reference MCP tool returns the stored JSON value for AI agents to interpret contextually.

Current Issue

The Rust code tries to deserialize arbitrary JSON into a rigid ReferenceContext struct instead of storing it as generic serde_json::Value. This breaks the intended generic architecture.

Solution: Use serde_json::Value throughout the storage and retrieval pipeline.

Discuss in Symposium

How users can quickly share code context with AI assistants running in terminals by selecting code and clicking the chat icon.

User Experience

Goal: Make it effortless to give an AI assistant context about specific code without manual copying/pasting or complex explanations.

Workflow:

  1. User selects relevant code in VSCode editor
  2. Clicks the chat icon in the status bar or editor
  3. A compact reference appears in their active AI terminal(s)
  4. User can immediately ask questions about that code

User Flow

  1. Select Code: User highlights code they want to discuss
  2. Click Chat Icon: Triggers reference creation and distribution
  3. Reference Created: System generates <symposium-ref id="..."/> and stores context
  4. Auto-Route to Terminal: Reference sent to active AI-enabled terminals
  5. Immediate Use: User can ask AI about the code using the reference

Terminal Selection Logic

The extension automatically determines where to send the reference:

Single AI Terminal: Sends reference directly, no user interaction needed Multiple AI Terminals: Sends to all terminals (user sees reference in each) No AI Terminals: Shows warning to start an MCP-enabled terminal

Terminal Discovery: Extension tracks active terminals through:

  • Marco/Polo discovery protocol with MCP servers
  • Shell PID matching between VSCode terminals and MCP processes
  • Real-time registry updates as terminals start/stop

Implementation Details

Reference Distribution: The extension sends the compact reference (<symposium-ref id="..."/>) directly to terminal stdin, making it appear as if the user typed it. This provides immediate visual feedback and allows the user to see exactly what will be sent to the AI.

Multi-Window Support:
Each VSCode window maintains its own terminal registry through the global daemon, ensuring references are routed correctly even with multiple VSCode instances.

Error Handling:

  • No AI Terminals: User gets clear warning message
  • Terminal Discovery Failure: Graceful degradation with manual terminal selection
  • Reference Storage Failure: User sees error but can retry

Message Flow

sequenceDiagram
    participant User as User
    participant Ext as VSCode Extension  
    participant Daemon as Message Bus Daemon
    participant Term as Terminal
    participant AI as AI Assistant

    User->>Ext: Select code + click chat icon
    Ext->>Daemon: store_reference(uuid, context) 
    Daemon->>Daemon: Store in reference system
    Daemon->>Ext: Response (success confirmation)
    Ext->>Term: Type <symposium-ref id="uuid"/> in terminal
    User->>AI: Ask question about the code
    AI->>Daemon: expand_reference(uuid)
    Daemon->>AI: Return full context
    AI->>User: Response with code understanding

This feature builds on the Symposium Reference System for context storage and retrieval.

Key Files

  • symposium/vscode-extension/src/extension.ts - Chat icon handling, terminal selection, reference distribution

Code Walkthroughs

Walkthroughs are interactive markdown documents that help explain code changes, architectural decisions, and system behavior. They combine standard markdown with specialized XML elements that can reference code locations, embed git diffs, and provide interactive elements.

Overview

The walkthrough system consists of three main components:

  1. Markdown + XML Format: Authors write walkthroughs using markdown with embedded XML elements (<comment>, <action>, <mermaid>)
  2. Server-side Parser: The MCP server's walkthrough_parser module converts the markdown to HTML, resolving code locations and generating interactive elements
  3. VSCode Integration: The VSCode extension renders the processed HTML in a webview with click handlers and interactive features

System Architecture

flowchart TD
    A["Markdown + XML Elements"] -->|"Raw markdown<br/>with XML tags"| B["Walkthrough Parser<br/>(MCP Server)"]
    B -->|"Processed HTML<br/>with data attributes"| C["VSCode Webview<br/>Extension"]
    
    B -->|"Dialect expressions<br/>(location attributes)"| D["Dialect Interpreter<br/>(Code Queries)"]
    D -->|"Resolved locations<br/>(file paths & line numbers)"| B
    
    C -->|"User interactions<br/>(clicks, navigation)"| E["Click Handlers &<br/>Interactive Features<br/>(Comments, Actions, etc.)"]

Processing Pipeline

When a walkthrough is presented:

  1. Parsing: The WalkthroughParser uses pulldown_cmark to parse markdown while identifying XML elements
  2. Resolution: Dialect expressions in location attributes are evaluated to find code locations
  3. HTML Generation: XML elements are converted to styled HTML with embedded data for interactivity
  4. VSCode Rendering: The extension displays the HTML in a webview and attaches click handlers

Walkthrough Format Overview

Walkthroughs are authored as standard markdown documents with embedded XML elements for interactive features:

  • <comment location="..."> - Contextual comments at specific code locations
  • <action button="..."> - Interactive buttons for follow-up tasks
  • <mermaid> - Architecture diagrams and flowcharts

The location attributes use Dialect expressions to target code locations (e.g., findDefinition("MyClass"), search("src/auth.rs", "async fn")).

For complete format specification and usage guidelines, see the AI guidance documentation.

Technical Implementation

Parsing Process

The WalkthroughParser in symposium/mcp-server/src/walkthrough_parser.rs handles the conversion from markdown+XML to interactive HTML:

  1. Markdown Parsing: Uses pulldown_cmark to parse markdown into a stream of events
  2. XML Detection: Identifies inline and block-level XML elements (<comment>, <action>, <mermaid>)
  3. Sequential Processing: Processes events one by one, collecting content between opening and closing tags
  4. Element Resolution: For each XML element:
    • Parses attributes using quick_xml
    • Evaluates Dialect expressions in location attributes via DialectInterpreter
    • Resolves code locations to file paths and line numbers
    • Generates structured data for client-side interaction
  5. HTML Generation: Converts resolved elements to styled HTML with embedded JSON data

Element Resolution Examples

Comment Element Processing

Input:

<comment location="findDefinition(`validateToken`)" icon="lightbulb">
This function validates authentication tokens
</comment>

Internal processing:

  1. Parse XML attributes: location="findDefinition(validateToken)", icon="lightbulb"
  2. Evaluate Dialect expression: findDefinition(validateToken)[{definedAt: {path: "src/auth.rs", start: {line: 42, column: 0}, ...}, ...}]
  3. Generate comment data with unique ID and normalized locations
  4. Create HTML with click handlers

Output HTML:

<div class="comment-item" data-comment='{"id":"comment-uuid","locations":[{"path":"src/auth.rs","line":42,"column":0,...}],"comment":["This function validates authentication tokens"]}' style="cursor: pointer; border: 1px solid var(--vscode-panel-border); ...">
    <div style="display: flex; align-items: flex-start;">
        <div class="comment-icon" style="margin-right: 8px;">💡</div>
        <div class="comment-content" style="flex: 1;">
            <div class="comment-locations" style="font-weight: 500; ...">src/auth.rs:42</div>
            <div class="comment-text">This function validates authentication tokens</div>
        </div>
    </div>
</div>

HTML Generation Strategy

The parser generates VSCode-compatible HTML with:

  • CSS Variables: Uses VSCode theme colors (var(--vscode-panel-border), var(--vscode-foreground), etc.)
  • Embedded Data: Stores structured data in data-* attributes for click handlers
  • Icon Mapping: Converts icon names to emoji representations
  • Location Display: Shows file paths and line numbers for easy navigation

VSCode Integration

Message Flow

  1. MCP Tool Call: AI agent calls present_walkthrough with markdown content
  2. Server Processing: MCP server parses and resolves the walkthrough to HTML
  3. Extension Delivery: VSCode extension receives processed HTML via IPC message
  4. Webview Rendering: Extension injects HTML into webview and attaches handlers

Extension Implementation

In symposium/vscode-extension/src/extension.ts:

if (message.type === 'present_walkthrough') {
    const walkthroughPayload = message.payload as PresentWalkthroughPayload;
    
    // Set base URI for file resolution
    this.walkthroughProvider.setBaseUri(walkthroughPayload.base_uri);
    
    // Show walkthrough HTML content in webview
    this.walkthroughProvider.showWalkthroughHtml(walkthroughPayload.content);
}

Webview Rendering

In symposium/vscode-extension/src/walkthroughWebview.ts:

// Inject server-rendered HTML directly
contentElement.innerHTML = message.content;

// Add placement icons to all dialectic links
addPlacementIcons();

// Process mermaid diagrams in the HTML content  
processMermaidDiagrams();

// Restore user interaction state
restoreOffscreenState();

Click Handler Registration

The extension automatically attaches click handlers to:

  • Comment elements: Navigate to code locations or show disambiguation dialog
  • Action buttons: Send messages back to the AI agent
  • Mermaid diagrams: Process and render using mermaid.js

Data Flow Example

For a complete walkthrough processing flow:

sequenceDiagram
    participant AI as AI Agent
    participant MCP as MCP Server
    participant VS as VSCode Extension
    participant User as User

    AI->>MCP: present_walkthrough({<br/>content: "# Changes<br/><comment location='findDefinition(`User`)'>..."<br/>})
    
    Note over MCP: Processing Pipeline
    MCP->>MCP: 1. Parse markdown with pulldown_cmark
    MCP->>MCP: 2. Identify XML: <comment location=...>
    MCP->>MCP: 3. Evaluate: findDefinition(`User`)<br/>→ [{path:"src/user.rs", line:10}]
    MCP->>MCP: 4. Generate HTML with data attributes
    
    MCP->>VS: Send processed HTML via IPC
    
    Note over VS: Webview Rendering
    VS->>VS: 1. Inject into webview: innerHTML = ...
    VS->>VS: 2. Attach click handlers to comments
    VS->>VS: 3. Process mermaid diagrams
    
    User->>VS: Click on comment
    VS->>VS: Navigate to src/user.rs:10

Walkthrough Format Implemented

Note: Walkthrough format documentation is implemented in the MCP server but not currently extracted with anchors.

For complete format specification and usage guidelines, see the walkthrough parser implementation in the MCP server.

Walkthrough Comment Interactions

This document describes the design for interactive comment features in code walkthroughs, allowing users to reply to walkthrough comments and have those replies forwarded to the AI agent.

When a walkthrough is presented

  • Unambiguous comments (exactly one location) are added into the VSCode comment system immediately.
  • Ambiguous comments: when the user clicks, they are presented with a dialogue to select how to resolve.
    • Once they select an option, the comment is rewritten to render as file.rs:25 and the comment is placed.
    • A magnifying glass icon remains that, if clicked, will allow the user to reselect the comment placement.
      • When comment placement changes, the comment is moved to the new location in VSCode.

Comment display

Comments display as if they were authored by "AI Agent" -- we should let AI agents customize their names later.

Comments have a "reply" button. When clicked, it inserts a <symposium-ref> that maps to a JSON blob like:

{
    "in-reply-to-comment-at": {
        "file": "path/to/file.js",
        "start": {
            "line": 22,
        },
        "end": {
            "line": 44,
        },
        "comment": "... the text that the AI placed at this location ..."
    }
}

This is inserted into the AI chat and the user can type more.

User-added comments

VSCode includes a CommentProvider that is meant to permit users to add comments on specific lines. Symposium registers as a comment provider but comments added by the user are written into the AI Agent chat instead.

Dialect Language

The Dialect language is a superset of JSON with function call syntax for expressing and composing IDE operations. Any valid JSON is also valid Dialect.

Design Goals

Dialect is designed to be LLM-friendly - the syntax should feel natural and familiar to language models, matching the kind of pseudo-code they would generate intuitively:

  • Function call syntax: findDefinitions("MyClass") reads like natural pseudo-code
  • JSON superset: We accept JSON augmented with function calls but we are also tolerant of trailing commas, unquoted field names

The goal is to minimize the gap between "what an LLM wants to express" and "valid Dialect syntax", making code generation more reliable and the language more intuitive.

Quick Start

Find where a symbol is defined:

findDefinitions("MyFunction")

Find all references to a symbol:

findReferences("MyClass")

Get information about a symbol:

getSymbolInfo("methodName")

Composition - find references to all definitions:

findReferences(findDefinitions("MyFunction"))

Grammar

Program = Expr

Expr = FunctionCall
     | JsonObject  
     | JsonArray
     | JsonAtomic

FunctionCall = Identifier "(" ArgumentList? ")"

ArgumentList = Expr ("," Expr)* ","?

JsonObject = "{" (JsonProperty ("," JsonProperty)* ","?)? "}"
JsonProperty = (String | Identifier) ":" Expr

JsonArray = "[" (Expr ("," Expr)* ","?)? "]"

JsonAtomic = Number | String | Boolean | "null" | "undefined"

Identifier = [a-zA-Z_][a-zA-Z0-9_]*
String = "\"" ... "\""  // JSON string literal
Number = ...            // JSON number literal  
Boolean = "true" | "false"

Function Signatures

Functions are called with positional arguments in a defined order:

Core IDE Operations

  • findDefinitions(symbol: string) - Find where a symbol is defined
  • findReferences(symbol: string) - Find all references to a symbol
  • getSymbolInfo(symbol: string) - Get detailed symbol information

Search Operations

  • searchFiles(pattern: string, path?: string) - Search for text patterns
  • findFiles(namePattern: string, path?: string) - Find files by name

Dynamic Semantics

A Dialect expression E evaluates to a JSON value:

Function Calls

  • If E = Identifier(Expr...), then:
    • Evaluate each Expr to values V...
    • Look up the function Identifier
    • Call the function with positional arguments V...
    • Return the function's result

JSON Values

  • If E = [ Expr... ], evaluate each Expr to V... and return [ V... ]
  • If E = { Property... }, evaluate each property value and return the object
  • If E = number | string | boolean | null | undefined, evaluate to itself

Implementation

The parser is implemented in dialect/parser.rs. The interpreter in dialect.rs handles function dispatch.

Defining Functions

Functions implement the DialectFunction trait with parameter order specification:

#![allow(unused)]
fn main() {
pub trait DialectFunction<U: Send>: DeserializeOwned + Send {
    type Output: Serialize + Send;

    const PARAMETER_ORDER: &'static [&'static str];

    async fn execute(self, interpreter: &mut DialectInterpreter<U>)
    -> anyhow::Result<Self::Output>;
}
}

Functions that represent values can implement DialectValue instead:

#![allow(unused)]

fn main() {
}

Error Handling

The parser provides detailed error messages with source location indicators:

error: Expected ')'
  |
1 | findDefinitions("MyClass"
  |                          ^
  |

Symposium application specifics

Startup and Window Management Draft: tile-based-window-management

Symposium maintains exactly one window open at any time, using three distinct window types for different application states.

Single Source of Truth Architecture

Critical Change: The application now uses AppDelegate as the single source of truth for ProjectManager instances.

Key Principles

  1. Single Owner: Only AppDelegate.currentProjectManager stores the ProjectManager
  2. Observer Pattern: All views get ProjectManager via @EnvironmentObject var appDelegate: AppDelegate
  3. Graceful Degradation: Views handle nil ProjectManager by showing "No project selected" state
  4. Clean Lifecycle: Setting currentProjectManager = nil automatically updates all views

Component Architecture

// ✅ CORRECT: Single source of truth
struct ProjectView: View {
    @EnvironmentObject var appDelegate: AppDelegate
    
    var body: some View {
        if let projectManager = appDelegate.currentProjectManager {
            // Use projectManager here
        } else {
            Text("No project selected")
        }
    }
}

// ❌ OLD: Direct references create cleanup issues
struct ProjectView: View {
    let projectManager: ProjectManager  // Creates duplicate reference
}

This architecture prevents reference leaks and ensures clean ProjectManager lifecycle management.

Three-Window Architecture

The application uses three distinct window types for different application states:

  1. Settings Window - for permissions and agent configuration
  2. Project Selection Window - for creating or opening projects
  3. Project Window - the main project workspace

Startup State Machine

The application follows a deterministic state machine on startup and window transitions:

stateDiagram-v2
    [*] --> AppStart
    AppStart --> Settings : Missing permissions
    AppStart --> CheckProject : Has permissions
    Settings --> AppStart : Window closed
    CheckProject --> OpenProject : Valid current project
    CheckProject --> ChooseProject : No current project
    ChooseProject --> AppStart : Window closed (no selection)
    ChooseProject --> CheckProject : Project path set
    CheckProject --> OpenProject : Valid project path
    OpenProject --> AppStart : Window closed (clears current project)

State Descriptions

AppStart: Entry point that validates permissions and current project state

  • If the correct window for the current state is already open, takes no action
  • Checks accessibility and screen recording permissions
  • If missing permissions → Settings Window
  • If has permissions → CheckProject

Settings: Regular window for permission management and agent configuration

  • User grants required permissions
  • User configures available agents
  • When closed → AppStart (re-validates permissions)

ChooseProject: Project selection and creation interface

  • Lists available agents with refresh capability
  • Provides new project creation form
  • Provides "Open existing project" file picker
  • When project created or selected → sets activeProjectPath and dismisses
  • Main app flow detects path change → CheckProject
  • When closed without selection → AppStart

CheckProject: Validates the persisted current project

  • Checks if current project path exists and is valid
  • If valid project → OpenProject
  • If no/invalid project → ChooseProject

OpenProject: Main project workspace window

  • Displays project taskspaces and management interface
  • When closed → clears current project and goes to AppStart

Window Management Implementation

Single Window Principle

The application typically maintains one window at a time, with the exception that Settings can be opened from any state:

func openWindow(id: String) {
    if id != "settings" {
        closeAllWindows()
    }
    // Open the requested window
}

func appStart() {
    // If the project window is open and we have a valid current project, do nothing
    if isProjectWindowOpen() && hasValidCurrentProject() {
        return
    }
    
    // If settings window is open, do nothing (user is configuring)
    if isSettingsWindowOpen() {
        return
    }
    
    // Normal startup logic...
    if !hasRequiredPermissions {
        openWindow(id: "settings")
    } else if let validCurrentProject = validateCurrentProject() {
        openWindow(id: "open-project") 
    } else {
        openWindow(id: "choose-project")
    }
}

Window Close Handling

Window close handling requires careful distinction between "user clicked close button" and "window disappeared due to app shutdown":

.onReceive(NotificationCenter.default.publisher(for: NSWindow.willCloseNotification)) { notification in
    // Only clear project when user explicitly closes the window
    if let window = notification.object as? NSWindow,
       window.identifier?.rawValue == "open-project" {
        // Clear project and return to startup flow
        appDelegate.currentProjectManager = nil
        settingsManager.activeProjectPath = ""
        appStart()
    }
}
.onDisappear {
    // NOTE: We don't handle project cleanup here because onDisappear
    // fires both when user closes window AND when app quits.
    // We only want to clear the project on explicit user close.
}

This ensures the project path persists between app launches when the user quits the app, but gets cleared when they explicitly close the project window.

Project Persistence

Current Project Storage

The current project is persisted in UserDefaults:

@AppStorage("activeProjectPath") var activeProjectPath: String = ""

Project Metadata

Each project stores metadata in project.json:

{
  "version": 1,
  "id": "uuid-string",
  "name": "Project Name",
  "gitURL": "https://github.com/user/repo.git",
  "directoryPath": "/path/to/Project.symposium",
  "agent": "claude-code",
  "defaultBranch": null,
  "createdAt": "2025-01-01T00:00:00Z",
  "taskspaces": []
}

Default Branch Handling

The defaultBranch field controls which branch new taskspaces start from:

  • If defaultBranch is specified: Use that remote/branch (e.g., main, origin/foo, etc.)
  • If defaultBranch is null/empty: Auto-detect the default branch from origin remote (e.g., origin/main)
  • Auto-detection uses git symbolic-ref refs/remotes/origin/HEAD or falls back to origin/main
  • New taskspace worktrees are created from the specified or detected remote branch

Validation Logic

On startup, the application validates the current project:

  1. Check if activeProjectPath exists as directory
  2. Check if project.json exists and is valid JSON
  3. Check if version number is supported
  4. If any validation fails → clear current project, go to ChooseProject

Project Creation and Opening Flow

Project Creation

The ChooseProject window includes a comprehensive project creation form that:

  1. Collects project metadata (name, git URL, directory, agent, etc.)
  2. Creates the project directory structure and project.json
  3. Sets activeProjectPath to the new project directory
  4. Dismisses the dialog
  5. Main app flow detects the path change and validates/opens the project

Project Opening

The project opening flow:

  1. User selects existing project directory via file picker
  2. Sets activeProjectPath to the selected directory
  3. Dismisses the dialog
  4. Main app flow detects the path change and validates/opens the project

Single ProjectManager Creation Point

This architecture ensures that ProjectManager instances are only created in one place - the main app flow when validating and opening projects. The dialogs are purely UI for collecting user input and setting the project path.

New Project Form

The ChooseProject window includes a comprehensive project creation form:

  • Project Name: Used for directory name (Name.symposium)
  • SSH Host: Currently localhost only (placeholder for future remote projects)
  • Directory Location: Parent directory with browse button
  • Origin Git Repository: Initial repository to clone
  • Additional Remotes: Extra git remotes with custom names
  • Editor: VSCode only (placeholder for future editor support)
  • AI Agent: Selection from available agents, including "None" option

Advanced Settings (collapsible section):

  • Default Branch for New Taskspaces: Branch name to use when creating new taskspaces (defaults to origin's default branch if empty)

Agent Selection

The agent selection uses the same widget as Settings but in selectable mode:

  • Lists all available agents from AgentManager.availableAgents
  • Includes "Refresh" button to rescan for agents
  • Includes "None" option for projects without AI assistance
  • Selected agent is stored in project metadata

Project Directory Structure

New projects create this structure:

ProjectName.symposium/
├── project.json          # Project metadata
├── .git/                 # Bare git repository
└── taskspaces/           # Individual taskspace directories

Error Handling

Validation Failures

When validation fails during CheckProject:

  • Log the specific failure reason
  • Show user notification explaining what happened
  • Clear activeProjectPath
  • Proceed to ChooseProject

Missing Agents

If a project's selected agent is no longer available:

  • Allow the project to open (don't block on missing agents)
  • Show warning in project window about missing agent
  • User will discover the issue when attempting to use agent features

Corrupted Project Files

If project.json is corrupted or unreadable:

  • Show user notification: "Project file corrupted, please select a different project"
  • Treat as validation failure
  • Clear current project and go to ChooseProject
  • User can attempt to recover or create new project

Implementation Notes

Window IDs

The application uses four SwiftUI WindowGroup IDs:

  • "splash" - Startup coordinator window (auto-opened by SwiftUI, immediately closes itself)
  • "settings" - Settings window
  • "choose-project" - Project selection window
  • "open-project" - Main project window

Startup Window Handling

Since SwiftUI automatically opens the first WindowGroup, the splash window serves as a startup coordinator:

WindowGroup(id: "splash") {
    SplashCoordinatorView()
        .onAppear {
            // Brief delay to show the amusing message
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.8) {
                appStart()
                closeWindow(id: "splash")
            }
        }
}

The splash window:

  • Opens automatically when the app launches
  • Shows a brief Socratic-themed message (e.g., "Preparing the symposium...", "Gathering the philosophers...", "Arranging the dialogue...", "Calling the assembly to order...")
  • Triggers the appStart() logic after a short delay
  • Closes itself once startup is complete
  • Never reopens during the app session

Race Condition Resolution

A critical implementation challenge was a race condition where project restoration happened before agent scanning completed, causing "waiting for daemon" issues. The solution was to wait for agentManager.scanningCompleted before attempting project restoration:

private func runStartupLogic() {
    // Check if agents are ready (needed for project restoration)
    if !agentManager.scanningCompleted {
        Logger.shared.log("App: Agent scan not complete, waiting...")
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
            self.runStartupLogic()
        }
        return
    }
    // ... rest of startup logic
}

Automatic Window Refresh

To improve user experience, the system automatically calls reregisterWindows() after project restoration to re-establish VSCode window connections without requiring manual refresh button clicks:

// Automatically refresh window connections on startup
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
    self.reregisterWindows(for: projectManager)
}

State Coordination

The main App struct acts as the coordinator:

  • No separate AppCoordinator class needed
  • appStart() function handles all state logic
  • State is implicit in the currently executing code path

Permission Checking

Reuses existing permission management:

  • permissionManager.hasAccessibilityPermission
  • permissionManager.hasScreenRecordingPermission
  • permissionManager.checkAllPermissions()

This architecture provides a clean, predictable user experience while maintaining the flexibility to extend functionality in the future.

Agent Initialization Integration

The startup and window management system integrates with the MCP-based agent initialization system documented in @guidance-and-initialization.md.

Agent Startup Flow

When a taskspace is opened in VSCode:

  1. VSCode Extension detects taskspace directory and loads MCP server
  2. MCP Server connects to Symposium daemon via IPC
  3. Agent requests initialization via expand_reference("yiasou")
  4. MCP Server fetches taskspace state using unified TaskspaceState protocol
  5. Dynamic prompt assembly includes real taskspace context (name, description, initial_prompt)
  6. Agent receives comprehensive initialization with embedded guidance and project context

TaskspaceState Protocol Integration

The window management system works with the TaskspaceState protocol:

  • Read operations: Agent initialization fetches current taskspace state
  • Write operations: update_taskspace tool modifies taskspace properties and clears initial_prompt
  • State transitions: Taskspace automatically transitions from hatchlingresume after first agent interaction
  • UI updates: Changes are persisted to disk and reflected in the Symposium GUI

Coordination with Project Manager

The ProjectManager handles TaskspaceState IPC messages:

func handleTaskspaceState(_ payload: TaskspaceStateRequest, messageId: String) async
    -> MessageHandlingResult<TaskspaceStateResponse>
{
    // Handle both read and write operations
    // Update taskspace properties if provided
    // Return current state with appropriate initial_prompt value
    // Persist changes and update UI
}

This integration ensures seamless coordination between the GUI application's window management and the agent's dynamic initialization system.

Stacked Windows

Overview

Stacked windows is a per-project feature that creates a "deck of cards" effect where all taskspace windows occupy the same screen position. When enabled, clicking a taskspace brings its window to the front while positioning all other taskspace windows at the exact same location behind it. This creates a clean, organized workspace where users can quickly switch between taskspaces without window clutter.

User Experience

Basic Behavior

  • Per-project setting: Each project can independently enable/disable stacked windows via a checkbox in the project header
  • Persistent storage: Setting is stored in project.json and travels with the project
  • Normal mode: When disabled, clicking taskspaces focuses windows normally
  • Stacked mode: When enabled, clicking a taskspace brings it to front and positions all other taskspace windows at the same location

Window Following

When stacked windows is enabled and the user interacts with any window in the stack:

  • Drag following: All stacked windows move together as a cohesive unit during drag operations
  • Resize following: All stacked windows resize to match when any window is resized
  • Following happens during the operation at 20fps (50ms intervals)
  • No manual repositioning needed - the illusion of a single window is maintained

Technical Architecture

Core Components

ProjectManager Integration

  • stackedWindowsEnabled property added to Project model (version 2)
  • setStackedWindowsEnabled() method updates setting and saves to disk
  • focusWindowWithStacking() implements the core stacking logic

WindowStackTracker

  • Handles drag and resize detection with window following
  • Uses AeroSpace-inspired event-driven approach
  • Manages peer window relationships (no leader/follower hierarchy)

Implementation Philosophy

The implementation follows research documented in:

Why Not AXObserver?

Traditional macOS window management relies on AXObserver notifications for tracking window movement. However, this approach has significant reliability issues:

  • Only ~30% application compatibility
  • Frequent notification failures
  • Complex state management
  • Performance overhead from continuous monitoring

AeroSpace-Inspired Solution

Instead, we use an event-driven polling approach:

  1. CGEvent Tap: System-wide mouse event detection
  2. Interaction Detection: Identify when user starts dragging or resizing any tracked window
  3. Timer-Based Polling: Track active window position and size only during interactions (50ms intervals)
  4. Synchronous Following: Move and resize all other windows to match the active window

This provides:

  • 90%+ application compatibility
  • Reliable cross-application window management
  • Minimal CPU overhead (polling only during interactions)
  • Low latency response (20fps during operations)

Data Storage

Project Schema Evolution

Stacked windows required updating the project schema from version 1 to version 2:

struct Project: Codable {
    let version: Int                    // Now 2
    // ... existing fields ...
    var stackedWindowsEnabled: Bool = false  // New field
}

Migration Strategy

The implementation includes proper migration logic:

  • Version 1 projects automatically upgrade to version 2
  • stackedWindowsEnabled defaults to false for migrated projects
  • Version 0 (legacy) projects also supported through fallback migration

This establishes a clean precedent for future schema upgrades.

Implementation Details

Window Positioning and Synchronization

When a taskspace is activated in stacked mode:

  1. Focus Target: Bring target window to front using standard macOS APIs
  2. Get Bounds: Retrieve target window position and size
  3. Position Others: Move all other taskspace windows to exact same bounds
  4. Start Tracking: Initialize interaction detection for all windows in the stack

Interaction Following Process

// Simplified flow
func handleMouseEvent(type: CGEventType, event: CGEvent) {
    switch type {
    case .leftMouseDown:
        if trackedWindowIDs.contains(clickedWindow) {
            startTracking(activeWindow: clickedWindow) // Begin 50ms polling
        }
    case .leftMouseUp:
        stopActiveTracking()   // End polling
    }
}

func updateOtherWindows() {
    let positionDelta = currentPosition - lastPosition
    let hasResize = currentSize != lastSize
    
    for otherWindow in otherWindows {
        if hasMovement && hasResize {
            moveAndResizeWindow(otherWindow, positionDelta: positionDelta, newSize: currentSize)
        } else if hasMovement {
            moveWindow(otherWindow, by: positionDelta)
        } else if hasResize {
            resizeWindow(otherWindow, to: currentSize)
        }
    }
}

Resource Management

  • Event Tap: Created once, reused across all tracking sessions
  • Timer: Only active during interaction operations (typically 1-3 seconds)
  • Cleanup: Automatic cleanup when disabling stacked mode or closing projects
  • Memory: Minimal overhead - just window ID tracking and position/size deltas

Edge Cases and Limitations

Current Limitations

  • IDE Windows Only: Currently applies only to VSCode windows (taskspace windows)
  • Single Stack: All taskspace windows form one stack (no multiple stacks)
  • Manual Recovery: If windows get out of sync, switching taskspaces re-aligns them

Handled Edge Cases

  • Window Closure: Stale window references are automatically cleaned up
  • Project Switching: Tracking stops when switching or closing projects
  • Mode Toggling: Disabling stacked windows stops all tracking
  • Application Crashes: Event tap and timers are properly cleaned up

Future Enhancements

Planned Improvements

  • Multiple Window Types: Extend to terminal windows, browser windows, etc.
  • Multiple Stacks: Support for organizing windows into different stacks
  • Visual Indicators: Subtle visual cues showing stack membership
  • Keyboard Shortcuts: Quick switching between stacked windows

Performance Optimizations

  • Adaptive Polling: Slower polling for small movements, faster for large movements
  • Movement Prediction: Anticipate window movement for smoother following
  • Batch Updates: Group multiple window moves into single operations

Success Metrics

The stacked windows implementation is considered successful based on:

  1. Movement Coherence: Dragging any window moves all others seamlessly ✅
  2. Resize Coherence: Resizing any window resizes all others to match ✅
  3. Visual Isolation: Only the active window is visible during normal operation ✅
  4. Reliable Switching: Users can switch between windows without position drift ✅
  5. System Stability: No performance impact or conflicts with macOS window management ✅
  6. Persistent Settings: Per-project configuration survives app restarts ✅

Conclusion

Stacked windows provides a clean, efficient way to manage multiple taskspace windows by creating the illusion of a single window that can be quickly switched between different contexts. The AeroSpace-inspired interaction detection ensures reliable window following across diverse applications while maintaining excellent performance characteristics.

The peer-based architecture allows any window in the stack to be the one the user interacts with, providing a natural and intuitive experience. Both drag and resize operations are synchronized across all windows in the stack, maintaining the illusion of working with a single window.

The implementation establishes solid patterns for future window management features and demonstrates how to build reliable cross-application window coordination on macOS.

Window Stacking Design

Problem Statement

In Symposium we wish to create the illusion of there being only one taskspace visible at a time. We achieve this by creating a "stack" of windows where the leader taskspace is on-top and the others ("followers") are underneath, making them invisible. The problem is that when the leader is moved, we need to ensure that the followers move as well or the illustion will be ruined. This document explores our design for achieving this in a smooth fashion.

Design Goals

  1. Single visible window: Only the active ("leader") window should be visible at any time
  2. Cohesive movement: When the leader window is moved, all stacked windows move together
  3. Reliable tracking: Changes in window position should be detected promptly and accurately
  4. Smooth transitions: Switching between windows in a stack should be fluid
  5. Minimal performance impact: Window tracking should not significantly impact system resources

Technical Approach

Leader/Follower Architecture

Each window stack consists of:

  • Leader window: The currently visible window that responds to user interaction
  • Follower windows: Hidden windows that track the leader's position

Only one window per stack acts as the leader at any time. All follower windows are positioned slightly inside the leader window's bounds to ensure they remain hidden even during movement lag.

Inset Positioning Strategy

Follower windows are positioned with a configurable inset relative to the leader window:

Leader window: (x, y, width, height)
Follower window: (x + inset, y + inset, width - 2*inset, height - 2*inset)

Default inset: 10% of window dimensions

Example calculation:

  • Leader at (100, 100) with size 1000×800
  • 10% inset = 100px horizontal, 80px vertical
  • Follower at (150, 140) with size 900×720

This inset serves multiple purposes:

  • Lag compensation: Even with 50-100ms notification delays, followers won't peek out during movement
  • Click protection: Prevents accidental interaction with hidden follower windows
  • Visual clarity: Makes the leader window unambiguously the active one

Movement Tracking with Event-Driven Polling

Note: This approach replaces the original AXObserver notification system which proved unreliable across macOS applications.

Window position tracking uses an AeroSpace-inspired event-driven polling system:

  1. CGEvent tap detects mouse clicks on any window system-wide
  2. Identify leader window by comparing clicked window with current stack leader
  3. Start timer-based polling (20ms interval) during drag operations
  4. Position delta calculation tracks leader movement and applies to followers
  5. Stop polling when drag operation completes

This approach provides:

  • 90%+ application compatibility (vs ~30% with AXObserver)
  • Minimal CPU overhead (polling only during active drags)
  • Low latency (20ms response time during movement)
  • Reliable detection across diverse application architectures

Leader Election and Handoff

When switching the active window in a stack:

  1. Stop position tracking for current leader window
  2. Resize and reposition old leader to follower dimensions/position
  3. Resize and reposition new leader to leader dimensions/position
  4. Raise new leader to top in window depth ordering
  5. Update leader reference for drag detection system

This handoff pattern ensures:

  • No conflicting position tracking during transitions
  • Clean separation between user-initiated and system-initiated position changes
  • Immediate activation of drag detection for new leader

Implementation Details

Configuration Options

ParameterDefaultRangeDescription
Inset percentage10%5-20%Follower window inset as percentage of leader size
Minimum inset10px5-50pxAbsolute minimum inset for very small windows
Maximum inset150px50-300pxAbsolute maximum inset for very large windows
Notification timeout200ms100-500msMax wait time for position updates

Edge Cases and Mitigations

Very Small Windows

  • Apply minimum absolute inset (10px) regardless of percentage
  • May result in minimal visual separation but preserves functionality

Very Large Windows

  • Apply maximum absolute inset (150px) to avoid excessive unused space
  • Maintains reasonable follower window usability

Manual Follower Movement

  • Followers moved manually (via Mission Control, etc.) are ignored
  • Position will be corrected on next leader switch
  • Alternative: Periodic position verification (future enhancement)

System-Initiated Movement

  • Display configuration changes may move all windows
  • Leader movement notifications will trigger follower repositioning
  • Natural recovery through normal tracking mechanism

Application Misbehavior

  • Some applications may resist programmatic repositioning
  • Error handling should gracefully exclude problematic windows from stacks
  • Logging available for debugging positioning failures

User Interface Integration

Stack Management Window

A separate monitoring window provides:

  • List of active stacks with window counts
  • Visual indication of current leader in each stack
  • Click-to-switch functionality
  • Future: Periodic thumbnail snapshots of stack contents

Visual Indicators

Current approach: Separate monitoring window Future possibilities:

  • Wider background window creating subtle drop shadow
  • Menu bar indicator with stack picker
  • Dock integration with notification badges

Performance Considerations

Event Detection Efficiency

  • Single CGEvent tap monitors system-wide mouse events
  • Event filtering occurs in kernel space for minimal overhead
  • Timer-based polling activated only during drag operations (typically 1-3 seconds)

Movement Latency

  • 20ms polling interval provides smooth 50fps tracking during drags
  • Sub-frame response time for typical window movements
  • Zero overhead when no drag operations are active

Memory Usage

  • Minimal overhead: notification observers and window position tracking
  • No additional window content rendering or capture required
  • Scales linearly with number of active stacks

Future Enhancements

Animated Transitions

  • Smooth resize/reposition animations during leader switches
  • Configurable animation duration and easing
  • May require Core Animation integration

Advanced Visual Indicators

  • Semi-transparent follower window previews
  • Stack depth indicators
  • Drag handles or manipulation widgets

Multi-Stack Management

  • Named stacks with persistence
  • Drag-and-drop between stacks
  • Keyboard shortcuts for stack navigation

Application Integration

  • VS Code extension for automatic window stacking
  • Terminal session integration
  • WebSocket API for programmatic control

Implementation Phases

Phase 1: Core Functionality ✓

  • Basic window stacking and switching
  • Manual add/remove from stacks
  • Simple position synchronization

Phase 2: Movement Tracking ✓

  • Implement kAXMovedNotification system (replaced with event-driven polling)
  • Implement AeroSpace-inspired drag detection with CGEvent taps
  • Add timer-based position tracking during active drags
  • Add inset positioning for followers
  • Create leader election and handoff logic

Phase 3: Enhanced UX

  • Stack monitoring window
  • Configuration options
  • Improved error handling and edge cases

Phase 4: Advanced Features

  • Animation system
  • Multiple stack support
  • External API integration

Success Criteria

The window stacking implementation will be considered successful when:

  1. Movement coherence: Dragging the leader window moves all followers seamlessly
  2. Visual isolation: Only the leader window is visible during normal operation
  3. Reliable switching: Users can switch between windows in a stack without position drift
  4. System stability: No performance impact or conflicts with macOS window management
  5. Edge case handling: Graceful behavior during display changes, app crashes, and unusual scenarios

This design provides a solid foundation for implementing true window stacking behavior while maintaining system compatibility and user experience quality.

Window Stacking Scenario Walkthrough

This document traces through a typical window stacking scenario, detailing the expected behavior and state changes at each step.

Initial Setup

  • Inset Percentage: 10% (0.10)
  • Minimum Inset: 10px
  • Maximum Inset: 150px

Scenario Steps

Step 1: Add Window 1 (Becomes Leader)

Action: User adds first Safari window to stack

Initial State:

Window 1 (Safari #115809):
  - Original Position: (673, 190)
  - Original Size: 574 × 614
  - Role: Not in stack

Operations:

  1. Store original frame: (673, 190, 574, 614)
  2. Mark as leader: isLeader = true
  3. Set as current leader window
  4. Setup drag detection for this window
  5. Focus window (bring to front)

Final State:

Window 1 (Safari #115809):
  - Position: (673, 190) [unchanged]
  - Size: 574 × 614 [unchanged]
  - Role: LEADER
  - Stored Original Frame: (673, 190, 574, 614)
  - Z-Order: Front

Stack: [Window 1 (LEADER)]


Step 2: Add Window 2 (Becomes Follower)

Action: User adds second Safari window to stack

Initial State:

Window 1 (Safari #115809):
  - Position: (673, 190)
  - Size: 574 × 614
  - Role: LEADER

Window 2 (Safari #115807):
  - Original Position: (729, 251)
  - Original Size: 574 × 491
  - Role: Not in stack

Calculations:

Leader Frame: (673, 190, 574, 614)

Horizontal Inset = max(10, min(150, 574 × 0.10)) = 57.4px
Vertical Inset = max(10, min(150, 614 × 0.10)) = 61.4px

Follower Frame:
  - X: 673 + 57.4 = 730.4
  - Y: 190 + 61.4 = 251.4
  - Width: 574 - (2 × 57.4) = 459.2
  - Height: 614 - (2 × 61.4) = 491.2

Operations:

  1. Store Window 2's original frame: (729, 251, 574, 491)
  2. Calculate follower frame based on current leader
  3. Move Window 2 to follower position: (730.4, 251.4, 459.2, 491.2)
  4. Mark as follower: isLeader = false
  5. Send Window 2 to back (behind leader)
  6. Do NOT switch leadership
  7. Do NOT focus Window 2

Final State:

Window 1 (Safari #115809):
  - Position: (673, 190) [unchanged]
  - Size: 574 × 614 [unchanged]
  - Role: LEADER
  - Z-Order: Front

Window 2 (Safari #115807):
  - Position: (730.4, 251.4)
  - Size: 459.2 × 491.2
  - Role: FOLLOWER
  - Stored Original Frame: (729, 251, 574, 491)
  - Z-Order: Behind Window 1

Stack: [Window 1 (LEADER), Window 2 (FOLLOWER)]


Step 3: User Drags Window 1

Action: User clicks and drags Window 1 by 50px right, 30px down

Initial State:

Window 1: Position (673, 190), Size (574, 614), LEADER
Window 2: Position (730.4, 251.4), Size (459.2, 491.2), FOLLOWER

Drag Detection:

  1. CGEvent tap detects mouse down on Window 1
  2. Verify it's our leader window (ID matches)
  3. Start PositionTracker with 20ms polling

During Drag (each 20ms poll):

Poll 1: Window 1 at (673, 190) → no change
Poll 2: Window 1 at (680, 193) → delta (+7, +3)
  - Move Window 2 to (737.4, 254.4)
Poll 3: Window 1 at (695, 201) → delta (+15, +8)
  - Move Window 2 to (745.4, 259.4)
...
Final: Window 1 at (723, 220) → delta (+28, +19)
  - Move Window 2 to (758.4, 270.4)

On Mouse Up:

  1. Stop PositionTracker
  2. Update stored positions

Final State:

Window 1 (Safari #115809):
  - Position: (723, 220) [moved +50, +30]
  - Size: 574 × 614 [unchanged]
  - Role: LEADER

Window 2 (Safari #115807):
  - Position: (780.4, 281.4) [moved +50, +30]
  - Size: 459.2 × 491.2 [unchanged]
  - Role: FOLLOWER

Step 4: User Clicks "Next" (Window 2 Becomes Leader)

Action: User clicks Next button to switch leadership

Initial State:

Window 1: Position (723, 220), Size (574, 614), LEADER
Window 2: Position (780.4, 281.4), Size (459.2, 491.2), FOLLOWER

Operations:

  1. Update leader flags:

    • Window 1: isLeader = false
    • Window 2: isLeader = true
  2. Resize Window 1 to follower size:

    Current Leader Frame: (723, 220, 574, 614)
    
    Horizontal Inset = 57.4px
    Vertical Inset = 61.4px
    
    New Window 1 Frame:
      - X: 723 + 57.4 = 780.4
      - Y: 220 + 61.4 = 281.4
      - Width: 574 - 114.8 = 459.2
      - Height: 614 - 122.8 = 491.2
    
  3. Restore Window 2 to its original size:

    Use stored original frame: (729, 251, 574, 491)
    
    But maintain current position relationship:
    - Window 2 was at (780.4, 281.4) as follower
    - Needs to expand back to original size (574 × 491)
    - New position: (723, 220) [same as old leader position]
    
  4. Apply changes:

    • Move/resize Window 1 to (780.4, 281.4, 459.2, 491.2)
    • Move/resize Window 2 to (723, 220, 574, 491)
    • Focus Window 2 (brings to front)
    • Send Window 1 to back
  5. Update drag detection:

    • Stop tracking Window 1
    • Start tracking Window 2

Final State:

Window 1 (Safari #115809):
  - Position: (780.4, 281.4)
  - Size: 459.2 × 491.2
  - Role: FOLLOWER
  - Z-Order: Back

Window 2 (Safari #115807):
  - Position: (723, 220)
  - Size: 574 × 491 [original width, original height]
  - Role: LEADER
  - Z-Order: Front

Key Design Decisions

Why Followers Are Smaller

  • Visual clarity: Even with lag, followers won't peek out
  • Click protection: User can't accidentally click on a follower
  • Depth perception: Smaller size reinforces the stacking metaphor

Position Management Strategy

  • Store original frames: Each window remembers its original size
  • Leader uses original size: When becoming leader, restore original dimensions
  • Followers use calculated size: Based on current leader's frame

Z-Order Management

  • Leader always on top: Use focus/raise actions
  • Followers always behind: Explicitly send to back
  • Order within followers: Doesn't matter as long as all are behind leader

Performance Considerations

  • Drag detection: Only poll during active drags (not constantly)
  • Batch updates: Move all followers in one operation if possible
  • Minimize resizing: Only resize when changing roles, not during drags

Current Implementation Issues

  1. Auto-leadership on add: Currently makes new windows leaders immediately
  2. Complex frame calculations: calculateLeaderFrame tries to reverse the follower calculation
  3. Missing z-order management: No explicit "send to back" for followers
  4. No stored original frames: Can't properly restore original window sizes

Proposed Fixes

  1. Remove auto-leadership: Keep new windows as followers
  2. Store original dimensions: Add originalSize field to WindowInfo
  3. Add z-order methods: Implement proper window layering
  4. Simplify calculations: Use stored frames instead of complex reversals

Taskspace Deletion System

Overview

Taskspace deletion is a complex operation that involves multiple safety checks, git cleanup, and coordination between the Swift app and VSCode extension. This document describes the architectural design and key insights.

System Architecture

Dialog Confirmation Flow

Preview: taskspace-deletion-dialog-confirmation

The deletion system now implements proper dialog confirmation to ensure agents receive accurate feedback:

Previous Flow (Problematic):

  1. Agent requests deletion → Immediate "success" response → UI dialog shown
  2. User could cancel, but agent already thought deletion succeeded

New Flow (Fixed):

  1. Agent requests deletion → No immediate response → UI dialog shown
  2. User confirms → Actual deletion → Success response to agent
  3. User cancels → Error response to agent ("Taskspace deletion was cancelled by user")

Key Implementation: The MessageHandlingResult::pending case allows the IPC system to defer responses until user interaction completes.

Safety-First Design

The deletion system prioritizes preventing data loss through a multi-layered safety approach:

  1. Fresh Branch Analysis: Computes git status when the deletion dialog opens (not when cached)
  2. Risk-Based Warnings: Shows specific warnings for different types of uncommitted work
  3. Smart Defaults: Auto-configures branch deletion toggle based on detected risks
  4. Graceful Fallbacks: Continues deletion even if git operations fail

Key Insight: Branch information must be computed fresh when the dialog appears, not when the app loads, because users may make commits between app startup and deletion attempts.

Cross-Process Coordination

The system coordinates between multiple processes:

  • Swift App: Manages deletion workflow and safety checks
  • VSCode Extension: Receives deletion broadcasts and closes windows gracefully
  • Git Commands: Handle worktree and branch cleanup operations

Planned Enhancement: Broadcast taskspace_will_delete messages before file removal to allow VSCode windows to close gracefully, preventing "file not found" errors.

Git Worktree Integration

Architectural Constraints

The system works within git worktree constraints:

  • Bare Repository: All git operations must run from the main repository directory
  • Worktree Paths: Include repository name (e.g., task-UUID/reponame/)
  • Shared Metadata: Multiple worktrees share one .git directory

Critical Design Decision: All git commands execute from project.directoryPath (bare repo) rather than individual worktree directories, because worktrees only contain symlinks to the main git metadata.

Directory Structure

project/
├── .git/                    # Bare repository (command execution context)
├── task-UUID1/
│   └── reponame/           # Git worktree (target for removal)
└── task-UUID2/
    └── reponame/           # Another worktree

Design Principles

  1. Safety First: Always warn about potential data loss before proceeding
  2. Accurate Agent Feedback: Only respond to agents after user makes actual decision
  3. Fresh Data: Compute branch info when needed, not when cached
  4. Clear Communication: Provide specific warnings for different risk types
  5. Graceful Degradation: Continue deletion even when git operations fail
  6. User Control: Let users choose branch deletion behavior based on clear information

IPC Message Flow

Preview: taskspace-deletion-dialog-confirmation

Deferred Response Pattern

The delete_taskspace IPC message uses a deferred response pattern:

  1. Request Received: handleDeleteTaskspace stores the message ID and returns .pending
  2. No Immediate Response: IPC manager doesn't send response yet
  3. Dialog Interaction: User confirms or cancels in UI
  4. Deferred Response: Appropriate success/error response sent based on user choice

This ensures the MCP server and agent receive accurate information about whether the deletion actually occurred.

Complexity Drivers

Why This System is Complex

  1. Git Worktree Management: Multiple worktrees sharing one repository with complex path relationships
  2. Safety vs Convenience: Balance between preventing data loss and smooth user experience
  3. Timing Dependencies: Fresh computation requirements vs performance considerations
  4. Cross-Process Coordination: Swift app + VSCode extension + git subprocess coordination
  5. Error Recovery: Graceful fallbacks when git operations fail due to various reasons

Key Architectural Insights

  1. Fresh computation of branch info prevents stale warnings that could mislead users
  2. Correct path resolution is critical - git commands must target actual worktree paths
  3. Separate warning types improve user understanding of different risks
  4. Execution context matters - git commands must run from bare repository directory

Testing Strategy

Critical Test Scenarios

  1. Clean State: No commits, no changes → Should show "safe to delete"
  2. Unmerged Work: Commits not in main → Should warn with commit count
  3. Uncommitted Work: Modified files → Should warn about uncommitted changes
  4. Mixed State: Both unmerged and uncommitted → Should show both warnings
  5. Git Operations: Verify worktree and branch removal work without errors
  6. Window Coordination: VSCode windows should close gracefully during deletion

Edge Cases to Consider

  1. Detached HEAD: How does branch detection behave?
  2. Merge Conflicts: What happens with unresolved conflicts in worktree?
  3. Permission Issues: How does system handle git command failures?
  4. Concurrent Access: What if multiple processes access same worktree?
  5. Network Issues: How does remote branch checking handle connectivity problems?

Implementation References

Key Methods (see code comments for implementation details):

  • ProjectManager.getTaskspaceBranchInfo() - Branch safety analysis
  • ProjectManager.deleteTaskspace() - Main deletion workflow
  • DeleteTaskspaceDialog - UI warning logic and user interaction

Critical Path Resolution (see deleteTaskspace() comments):

  • Worktree path calculation and git command targeting
  • Execution context setup for git operations

Safety Checking (see getTaskspaceBranchInfo() comments):

  • Git command details and error handling
  • Fresh computation timing and rationale

Persistent Agent Sessions

Enabling background, persistent AI agents that survive terminal disconnection

Overview

The Agent Process Manager enables persistent, asynchronous AI agents by wrapping CLI tools (Q CLI, Claude Code) in tmux sessions. This allows agents to:

  • Run in the background independently of terminal sessions
  • Persist across user disconnections
  • Be attached/detached at will
  • Continue work asynchronously

Architecture

graph TB
    User[User Terminal] -->|attach/detach| TmuxSession[tmux Session]
    AgentManager[Agent Manager] -->|spawn/kill| TmuxSession
    TmuxSession -->|runs| AgentCLI[Agent CLI Tool]
    AgentCLI -->|q chat --resume| MCPServer[MCP Server]
    MCPServer -->|IPC| Daemon[Symposium Daemon]
    
    AgentManager -->|persists| SessionFile[~/.symposium/agent-sessions.json]
    
    style TmuxSession fill:#e1f5fe
    style AgentManager fill:#f3e5f5
    style SessionFile fill:#e8f5e8

Usage

Spawn Agent Session

symposium-mcp agent spawn --uuid my-agent-1 --workdir /path/to/project q chat --resume

List Active Sessions

symposium-mcp agent list
# Output:
# Active agent sessions:
#   my-agent-1 - Running (symposium-agent-my-agent-1)

Attach to Session

symposium-mcp agent attach my-agent-1
# Output: To attach to agent session my-agent-1, run:
#   tmux attach-session -t symposium-agent-my-agent-1

# Then run the command:
tmux attach-session -t symposium-agent-my-agent-1

Kill Session

symposium-mcp agent kill my-agent-1

Implementation Details

Session Management

  • tmux Sessions: Each agent runs in a dedicated tmux session named symposium-agent-{uuid}
  • Metadata Storage: Session info persisted in ~/.symposium/agent-sessions.json
  • Auto-Sync: On startup, syncs with actual tmux sessions to handle crashes/restarts
  • Status Tracking: Monitors session state (Starting, Running, Crashed, Stopped)

Agent Lifecycle

  1. Spawn: Creates tmux session with agent command in specified working directory
  2. Monitor: Tracks session status and syncs with tmux reality
  3. Attach: Provides tmux attach command for user connection
  4. Detach: User can disconnect without killing agent (standard tmux behavior)
  5. Kill: Terminates tmux session and cleans up metadata

Session Persistence

#![allow(unused)]
fn main() {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentSession {
    pub uuid: String,
    pub tmux_session_name: String,
    pub agent_command: Vec<String>,
    pub working_directory: PathBuf,
    pub status: AgentStatus,
    pub created_at: SystemTime,
    pub last_attached: Option<SystemTime>,
}
}

Integration with Existing System

Current Flow (Synchronous)

VSCode Extension → Terminal → Agent CLI (foreground) → Dies with terminal

New Flow (Persistent)

VSCode Extension → Agent Manager → tmux Session → Agent CLI (background)
                                      ↑
User Terminal ────────────────────────┘ (attach/detach)

Conversation Persistence

  • CLI Tool Responsibility: Q CLI and Claude Code handle conversation history per directory
  • Directory-Based: q chat --resume resumes last conversation in working directory
  • No Additional State: Agent Manager doesn't duplicate conversation storage

Future Enhancements

Custom Pty Manager

Consider replacing tmux with custom Rust implementation using:

  • tty_spawn - Spawn processes in pseudo-terminals
  • teetty - Terminal session management

Benefits:

  • Eliminate tmux dependency
  • More control over session lifecycle
  • Custom attach/detach protocols
  • Better integration with Symposium

Conversation Identification

Enhance CLI tools to support named conversations:

q chat --list-conversations
q chat --resume conversation-id-123

Multi-Connection Support

Allow multiple users/terminals to connect to same agent session simultaneously.

Background Task Queue

Enable agents to work on tasks asynchronously while disconnected:

symposium-mcp agent queue my-agent-1 "Implement authentication system"

Error Handling

Session Recovery

  • Crashed Sessions: Detected during sync, marked as Crashed status
  • Orphaned Metadata: Sessions without tmux counterpart are cleaned up
  • Startup Sync: Reconciles stored sessions with actual tmux sessions

tmux Availability

  • Missing tmux: Commands fail gracefully with clear error messages
  • Permission Issues: Standard tmux error handling applies

Testing

Manual Testing

# Test basic lifecycle
symposium-mcp agent spawn --uuid test-1 --workdir /tmp sleep 30
symposium-mcp agent list
symposium-mcp agent attach test-1
# (attach and verify session works)
symposium-mcp agent kill test-1

# Test with real agent
symposium-mcp agent spawn --uuid q-test --workdir /path/to/project q chat
symposium-mcp agent attach q-test
# (interact with Q CLI, detach with Ctrl-B D, reattach)

Integration Testing

  • Verify agent CLI tools work correctly in tmux sessions
  • Test MCP server connectivity from tmux-spawned agents
  • Validate conversation persistence across attach/detach cycles

Security Considerations

Session Isolation

  • Each agent runs in separate tmux session
  • Working directory isolation per agent
  • No shared state between agent sessions

File Permissions

  • Session metadata stored in user home directory
  • Standard tmux socket permissions apply
  • No elevation of privileges required

Agent Manager

Symposium supports two execution models for AI agents:

Synchronous Agents (Current Default)

  • Execution: Agents run in VSCode integrated terminals as foreground processes
  • Lifecycle: Agent dies when terminal closes or VSCode exits
  • State: Conversation history managed by CLI tools per directory
  • Use Case: Interactive development sessions with direct terminal access

Persistent Agents (New Capability)

  • Execution: Agents run in background tmux sessions managed by Agent Manager
  • Lifecycle: Agents persist across terminal disconnections and VSCode restarts
  • State: Session metadata in ~/.symposium/agent-sessions.json, conversation history still managed by CLI tools
  • Use Case: Long-running tasks, asynchronous work, multi-session collaboration

Agent Manager Commands

# Spawn persistent agent session
symposium-mcp agent spawn --uuid my-agent --workdir /path/to/project q chat

# List active sessions  
symposium-mcp agent list

# Attach to running session
symposium-mcp agent attach my-agent

# Kill session
symposium-mcp agent kill my-agent

Persistent Agent Architecture

graph TB
    User[User Terminal] -->|attach/detach| TmuxSession[tmux Session]
    AgentManager[Agent Manager] -->|spawn/kill| TmuxSession
    TmuxSession -->|runs| AgentCLI[Agent CLI Tool]
    AgentCLI -->|q chat --resume| MCPServer[MCP Server]
    MCPServer -->|IPC| Daemon[Symposium Daemon]
    
    AgentManager -->|persists| SessionFile[~/.symposium/agent-sessions.json]
    
    style TmuxSession fill:#e1f5fe
    style AgentManager fill:#f3e5f5
    style SessionFile fill:#e8f5e8

Research reports

This section contains research reports that we have commissioned that give details of how things work. Sometimes its useful to point your agent at these reports so they can get the details into context.

Language Server Protocol (LSP) - Comprehensive Overview

Executive Summary

The Language Server Protocol (LSP) defines the protocol used between an editor or IDE and a language server that provides language features like auto complete, go to definition, find all references etc. The goal of the Language Server Index Format (LSIF, pronounced like "else if") is to support rich code navigation in development tools or a Web UI without needing a local copy of the source code.

The idea behind the Language Server Protocol (LSP) is to standardize the protocol for how tools and servers communicate, so a single Language Server can be re-used in multiple development tools, and tools can support languages with minimal effort.

Key Benefits:

  • Reduces M×N complexity to M+N (one server per language instead of one implementation per editor per language)
  • Enables language providers to focus on a single high-quality implementation
  • Allows editors to support multiple languages with minimal effort
  • Standardized JSON-RPC based communication

Table of Contents

  1. Architecture & Core Concepts
  2. Base Protocol
  3. Message Types
  4. Capabilities System
  5. Lifecycle Management
  6. Document Synchronization
  7. Language Features
  8. Workspace Features
  9. Window Features
  10. Implementation Considerations
  11. Version History

Architecture & Core Concepts

Problem Statement

Prior to the design and implementation of the Language Server Protocol for the development of Visual Studio Code, most language services were generally tied to a given IDE or other editor. In the absence of the Language Server Protocol, language services are typically implemented by using a tool-specific extension API.

This created a classic M×N complexity problem where:

  • M = Number of editors/IDEs
  • N = Number of programming languages
  • Total implementations needed = M × N

LSP Solution

The idea behind a Language Server is to provide the language-specific smarts inside a server that can communicate with development tooling over a protocol that enables inter-process communication.

Architecture Components:

  1. Language Client: The editor/IDE that requests language services
  2. Language Server: A separate process providing language intelligence
  3. LSP: The standardized communication protocol between them

Communication Model:

  • JSON-RPC 2.0 based messaging
  • A language server runs as a separate process and development tools communicate with the server using the language protocol over JSON-RPC.
  • Bi-directional communication (client ↔ server)
  • Support for synchronous requests and asynchronous notifications

Supported Languages & Environments

LSP is not restricted to programming languages. It can be used for any kind of text-based language, like specifications or domain-specific languages (DSL).

Transport Options:

  • stdio (standard input/output)
  • Named pipes (Windows) / Unix domain sockets
  • TCP sockets
  • Node.js IPC

This comprehensive overview provides the foundation for understanding and implementing Language Server Protocol solutions. Each section can be expanded into detailed implementation guides as needed.

Base Protocol

Message Structure

The base protocol consists of a header and a content part (comparable to HTTP). The header and content part are separated by a '\r\n'.

Header Format

Content-Length: <number>\r\n
Content-Type: application/vscode-jsonrpc; charset=utf-8\r\n
\r\n

Required Headers:

  • Content-Length: Length of content in bytes (mandatory)
  • Content-Type: MIME type (optional, defaults to application/vscode-jsonrpc; charset=utf-8)

Content Format

Contains the actual content of the message. The content part of a message uses JSON-RPC to describe requests, responses and notifications.

Example Message:

Content-Length: 126\r\n
\r\n
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "textDocument/completion",
  "params": {
    "textDocument": { "uri": "file:///path/to/file.js" },
    "position": { "line": 5, "character": 10 }
  }
}

JSON-RPC Structure

Base Message

interface Message {
  jsonrpc: string; // Always "2.0"
}

Request Message

interface RequestMessage extends Message {
  id: integer | string;
  method: string;
  params?: array | object;
}

Response Message

interface ResponseMessage extends Message {
  id: integer | string | null;
  result?: any;
  error?: ResponseError;
}

Notification Message

interface NotificationMessage extends Message {
  method: string;
  params?: array | object;
}

Error Handling

Standard Error Codes:

  • -32700: Parse error
  • -32600: Invalid Request
  • -32601: Method not found
  • -32602: Invalid params
  • -32603: Internal error

LSP-Specific Error Codes:

  • -32803: RequestFailed
  • -32802: ServerCancelled
  • -32801: ContentModified
  • -32800: RequestCancelled

Language Features

Language Features provide the actual smarts in the language server protocol. They are usually executed on a [text document, position] tuple. The main language feature categories are: code comprehension features like Hover or Goto Definition. coding features like diagnostics, code complete or code actions.

Go to Definition

textDocument/definition: TextDocumentPositionParams → Location | Location[] | LocationLink[] | null

Go to Declaration

textDocument/declaration: TextDocumentPositionParams → Location | Location[] | LocationLink[] | null

Go to Type Definition

textDocument/typeDefinition: TextDocumentPositionParams → Location | Location[] | LocationLink[] | null

Go to Implementation

textDocument/implementation: TextDocumentPositionParams → Location | Location[] | LocationLink[] | null

Find References

textDocument/references: ReferenceParams → Location[] | null

interface ReferenceParams extends TextDocumentPositionParams {
  context: { includeDeclaration: boolean; }
}

Information Features

Hover

textDocument/hover: TextDocumentPositionParams → Hover | null

interface Hover {
  contents: MarkedString | MarkedString[] | MarkupContent;
  range?: Range;
}

Signature Help

textDocument/signatureHelp: SignatureHelpParams → SignatureHelp | null

interface SignatureHelp {
  signatures: SignatureInformation[];
  activeSignature?: uinteger;
  activeParameter?: uinteger;
}

Document Symbols

textDocument/documentSymbol: DocumentSymbolParams → DocumentSymbol[] | SymbolInformation[] | null

Workspace Symbols

workspace/symbol: WorkspaceSymbolParams → SymbolInformation[] | WorkspaceSymbol[] | null

Code Intelligence Features

Code Completion

textDocument/completion: CompletionParams → CompletionItem[] | CompletionList | null

interface CompletionList {
  isIncomplete: boolean;
  items: CompletionItem[];
}

interface CompletionItem {
  label: string;
  kind?: CompletionItemKind;
  detail?: string;
  documentation?: string | MarkupContent;
  sortText?: string;
  filterText?: string;
  insertText?: string;
  textEdit?: TextEdit;
  additionalTextEdits?: TextEdit[];
}

Completion Triggers:

  • User invoked (Ctrl+Space)
  • Trigger characters (., ->, etc.)
  • Incomplete completion re-trigger

Code Actions

textDocument/codeAction: CodeActionParams → (Command | CodeAction)[] | null

interface CodeAction {
  title: string;
  kind?: CodeActionKind;
  diagnostics?: Diagnostic[];
  isPreferred?: boolean;
  disabled?: { reason: string; };
  edit?: WorkspaceEdit;
  command?: Command;
}

Code Action Kinds:

  • quickfix - Fix problems
  • refactor - Refactoring operations
  • source - Source code actions (organize imports, etc.)

Code Lens

textDocument/codeLens: CodeLensParams → CodeLens[] | null

interface CodeLens {
  range: Range;
  command?: Command;
  data?: any; // For resolve support
}

Formatting Features

Document Formatting

textDocument/formatting: DocumentFormattingParams → TextEdit[] | null

Range Formatting

textDocument/rangeFormatting: DocumentRangeFormattingParams → TextEdit[] | null

On-Type Formatting

textDocument/onTypeFormatting: DocumentOnTypeFormattingParams → TextEdit[] | null

Semantic Features

Semantic Tokens

Since version 3.16.0. The request is sent from the client to the server to resolve semantic tokens for a given file. Semantic tokens are used to add additional color information to a file that depends on language specific symbol information.

textDocument/semanticTokens/full: SemanticTokensParams → SemanticTokens | null
textDocument/semanticTokens/range: SemanticTokensRangeParams → SemanticTokens | null
textDocument/semanticTokens/full/delta: SemanticTokensDeltaParams → SemanticTokens | SemanticTokensDelta | null

Token Encoding:

  • 5 integers per token: [deltaLine, deltaStart, length, tokenType, tokenModifiers]
  • Relative positioning for efficiency
  • Bit flags for modifiers

Inlay Hints

textDocument/inlayHint: InlayHintParams → InlayHint[] | null

interface InlayHint {
  position: Position;
  label: string | InlayHintLabelPart[];
  kind?: InlayHintKind; // Type | Parameter
  tooltip?: string | MarkupContent;
  paddingLeft?: boolean;
  paddingRight?: boolean;
}

Diagnostics

Push Model (Traditional)

textDocument/publishDiagnostics: PublishDiagnosticsParams

interface PublishDiagnosticsParams {
  uri: DocumentUri;
  version?: integer;
  diagnostics: Diagnostic[];
}

Pull Model (Since 3.17)

textDocument/diagnostic: DocumentDiagnosticParams → DocumentDiagnosticReport
workspace/diagnostic: WorkspaceDiagnosticParams → WorkspaceDiagnosticReport

Diagnostic Structure:

interface Diagnostic {
  range: Range;
  severity?: DiagnosticSeverity; // Error | Warning | Information | Hint
  code?: integer | string;
  source?: string; // e.g., "typescript"
  message: string;
  tags?: DiagnosticTag[]; // Unnecessary | Deprecated
  relatedInformation?: DiagnosticRelatedInformation[];
}

Implementation Guide

Performance Guidelines

Message Ordering: Responses to requests should be sent in roughly the same order as the requests appear on the server or client side.

State Management:

  • Servers should handle partial/incomplete requests gracefully
  • Use ContentModified error for outdated results
  • Implement proper cancellation support

Resource Management:

  • Language servers run in separate processes
  • Avoid memory leaks in long-running servers
  • Implement proper cleanup on shutdown

Error Handling

Client Responsibilities:

  • Restart crashed servers (with exponential backoff)
  • Handle ContentModified errors gracefully
  • Validate server responses

Server Responsibilities:

  • Return appropriate error codes
  • Handle malformed/outdated requests
  • Monitor client process health

Transport Considerations

Command Line Arguments:

language-server --stdio                    # Use stdio
language-server --pipe=<n>             # Use named pipe/socket
language-server --socket --port=<port>    # Use TCP socket  
language-server --node-ipc                # Use Node.js IPC
language-server --clientProcessId=<pid>   # Monitor client process

Testing Strategies

Unit Testing:

  • Mock LSP message exchange
  • Test individual feature implementations
  • Validate message serialization/deserialization

Integration Testing:

  • End-to-end editor integration
  • Multi-document scenarios
  • Error condition handling

Performance Testing:

  • Large file handling
  • Memory usage patterns
  • Response time benchmarks

Advanced Topics

Custom Extensions

Experimental Capabilities:

interface ClientCapabilities {
  experimental?: {
    customFeature?: boolean;
    vendorSpecificExtension?: any;
  };
}

Custom Methods:

  • Use vendor prefixes: mycompany/customFeature
  • Document custom protocol extensions
  • Ensure graceful degradation

Security Considerations

Process Isolation:

  • Language servers run in separate processes
  • Limit file system access appropriately
  • Validate all input from untrusted sources

Content Validation:

  • Sanitize file paths and URIs
  • Validate document versions
  • Implement proper input validation

Multi-Language Support

Language Identification:

interface TextDocumentItem {
  uri: DocumentUri;
  languageId: string; // "typescript", "python", etc.
  version: integer;
  text: string;
}

Document Selectors:

type DocumentSelector = DocumentFilter[];

interface DocumentFilter {
  language?: string;    // "typescript"
  scheme?: string;      // "file", "untitled"  
  pattern?: string;     // "**/*.{ts,js}"
}

Message Reference

Message Types

Request/Response Pattern

Client-to-Server Requests:

  • initialize - Server initialization
  • textDocument/hover - Get hover information
  • textDocument/completion - Get code completions
  • textDocument/definition - Go to definition

Server-to-Client Requests:

  • client/registerCapability - Register new capabilities
  • workspace/configuration - Get configuration settings
  • window/showMessageRequest - Show message with actions

Notification Pattern

Client-to-Server Notifications:

  • initialized - Initialization complete
  • textDocument/didOpen - Document opened
  • textDocument/didChange - Document changed
  • textDocument/didSave - Document saved
  • textDocument/didClose - Document closed

Server-to-Client Notifications:

  • textDocument/publishDiagnostics - Send diagnostics
  • window/showMessage - Display message
  • telemetry/event - Send telemetry data

Special Messages

Dollar Prefixed Messages: Notifications and requests whose methods start with '$/' are messages which are protocol implementation dependent and might not be implementable in all clients or servers.

Examples:

  • $/cancelRequest - Cancel ongoing request
  • $/progress - Progress reporting
  • $/setTrace - Set trace level

Capabilities System

Not every language server can support all features defined by the protocol. LSP therefore provides 'capabilities'. A capability groups a set of language features.

Capability Exchange

During Initialization:

  1. Client announces capabilities in initialize request
  2. Server announces capabilities in initialize response
  3. Both sides adapt behavior based on announced capabilities

Client Capabilities Structure

interface ClientCapabilities {
  workspace?: WorkspaceClientCapabilities;
  textDocument?: TextDocumentClientCapabilities;
  window?: WindowClientCapabilities;
  general?: GeneralClientCapabilities;
  experimental?: any;
}

Key Client Capabilities:

  • textDocument.hover.dynamicRegistration - Support dynamic hover registration
  • textDocument.completion.contextSupport - Support completion context
  • workspace.workspaceFolders - Multi-root workspace support
  • window.workDoneProgress - Progress reporting support

Server Capabilities Structure

interface ServerCapabilities {
  textDocumentSync?: TextDocumentSyncKind | TextDocumentSyncOptions;
  completionProvider?: CompletionOptions;
  hoverProvider?: boolean | HoverOptions;
  definitionProvider?: boolean | DefinitionOptions;
  referencesProvider?: boolean | ReferenceOptions;
  documentSymbolProvider?: boolean | DocumentSymbolOptions;
  workspaceSymbolProvider?: boolean | WorkspaceSymbolOptions;
  codeActionProvider?: boolean | CodeActionOptions;
  // ... many more
}

Dynamic Registration

Servers can register/unregister capabilities after initialization:

// Register new capability
client/registerCapability: {
  registrations: [{
    id: "uuid",
    method: "textDocument/willSaveWaitUntil",
    registerOptions: { documentSelector: [{ language: "javascript" }] }
  }]
}

// Unregister capability
client/unregisterCapability: {
  unregisterations: [{ id: "uuid", method: "textDocument/willSaveWaitUntil" }]
}

Lifecycle Management

Initialization Sequence

  1. Client → Server: initialize request

    interface InitializeParams {
      processId: integer | null;
      clientInfo?: { name: string; version?: string; };
      rootUri: DocumentUri | null;
      initializationOptions?: any;
      capabilities: ClientCapabilities;
      workspaceFolders?: WorkspaceFolder[] | null;
    }
    
  2. Server → Client: initialize response

    interface InitializeResult {
      capabilities: ServerCapabilities;
      serverInfo?: { name: string; version?: string; };
    }
    
  3. Client → Server: initialized notification

    • Signals completion of initialization
    • Server can now send requests to client

Shutdown Sequence

  1. Client → Server: shutdown request

    • Server must not accept new requests (except exit)
    • Server should finish processing ongoing requests
  2. Client → Server: exit notification

    • Server should exit immediately
    • Exit code: 0 if shutdown was called, 1 otherwise

Process Monitoring

Client Process Monitoring:

  • Server can monitor client process via processId from initialize
  • Server should exit if client process dies

Server Crash Handling:

  • Client should restart crashed servers
  • Implement exponential backoff to prevent restart loops

Document Synchronization

Client support for textDocument/didOpen, textDocument/didChange and textDocument/didClose notifications is mandatory in the protocol and clients can not opt out supporting them.

Text Document Sync Modes

enum TextDocumentSyncKind {
  None = 0,        // No synchronization
  Full = 1,        // Full document sync on every change
  Incremental = 2  // Incremental sync (deltas only)
}

Document Lifecycle

Document Open

textDocument/didOpen: {
  textDocument: {
    uri: "file:///path/to/file.js",
    languageId: "javascript", 
    version: 1,
    text: "console.log('hello');"
  }
}

Document Change

textDocument/didChange: {
  textDocument: { uri: "file:///path/to/file.js", version: 2 },
  contentChanges: [{
    range: { start: { line: 0, character: 12 }, end: { line: 0, character: 17 } },
    text: "world"
  }]
}

Change Event Types:

  • Full text: Replace entire document
  • Incremental: Specify range and replacement text

Document Save

// Optional: Before save
textDocument/willSave: {
  textDocument: { uri: "file:///path/to/file.js" },
  reason: TextDocumentSaveReason.Manual
}

// Optional: Before save with text edits
textDocument/willSaveWaitUntil → TextEdit[]

// After save
textDocument/didSave: {
  textDocument: { uri: "file:///path/to/file.js" },
  text?: "optional full text"
}

Document Close

textDocument/didClose: {
  textDocument: { uri: "file:///path/to/file.js" }
}

Position Encoding

Prior to 3.17 the offsets were always based on a UTF-16 string representation. Since 3.17 clients and servers can agree on a different string encoding representation (e.g. UTF-8).

Supported Encodings:

  • utf-16 (default, mandatory)
  • utf-8
  • utf-32

Position Structure:

interface Position {
  line: uinteger;     // Zero-based line number
  character: uinteger; // Zero-based character offset
}

interface Range {
  start: Position;
  end: Position;
}

Workspace Features

Multi-Root Workspaces

workspace/workspaceFolders → WorkspaceFolder[] | null

interface WorkspaceFolder {
  uri: URI;
  name: string;
}

// Notification when folders change
workspace/didChangeWorkspaceFolders: DidChangeWorkspaceFoldersParams

Configuration Management

// Server requests configuration from client
workspace/configuration: ConfigurationParams → any[]

interface ConfigurationItem {
  scopeUri?: URI;     // Scope (file/folder) for the setting
  section?: string;   // Setting name (e.g., "typescript.preferences")
}

// Client notifies server of configuration changes
workspace/didChangeConfiguration: DidChangeConfigurationParams

File Operations

File Watching

workspace/didChangeWatchedFiles: DidChangeWatchedFilesParams

interface FileEvent {
  uri: DocumentUri;
  type: FileChangeType; // Created | Changed | Deleted
}

File System Operations

// Before operations (can return WorkspaceEdit)
workspace/willCreateFiles: CreateFilesParams → WorkspaceEdit | null
workspace/willRenameFiles: RenameFilesParams → WorkspaceEdit | null  
workspace/willDeleteFiles: DeleteFilesParams → WorkspaceEdit | null

// After operations (notifications)
workspace/didCreateFiles: CreateFilesParams
workspace/didRenameFiles: RenameFilesParams
workspace/didDeleteFiles: DeleteFilesParams

Command Execution

workspace/executeCommand: ExecuteCommandParams → any

interface ExecuteCommandParams {
  command: string;           // Command identifier
  arguments?: any[];         // Command arguments
}

// Server applies edits to workspace
workspace/applyEdit: ApplyWorkspaceEditParams → ApplyWorkspaceEditResult

Window Features

Message Display

Show Message (Notification)

window/showMessage: ShowMessageParams

interface ShowMessageParams {
  type: MessageType; // Error | Warning | Info | Log | Debug
  message: string;
}

Show Message Request

window/showMessageRequest: ShowMessageRequestParams → MessageActionItem | null

interface ShowMessageRequestParams {
  type: MessageType;
  message: string;
  actions?: MessageActionItem[]; // Buttons to show
}

Show Document

window/showDocument: ShowDocumentParams → ShowDocumentResult

interface ShowDocumentParams {
  uri: URI;
  external?: boolean;    // Open in external program
  takeFocus?: boolean;   // Focus the document
  selection?: Range;     // Select range in document
}

Progress Reporting

Work Done Progress

// Server creates progress token
window/workDoneProgress/create: WorkDoneProgressCreateParams → void

// Report progress using $/progress
$/progress: ProgressParams<WorkDoneProgressBegin | WorkDoneProgressReport | WorkDoneProgressEnd>

// Client can cancel progress
window/workDoneProgress/cancel: WorkDoneProgressCancelParams

Progress Reporting Pattern

// Begin
{ kind: "begin", title: "Indexing", cancellable: true, percentage: 0 }

// Report
{ kind: "report", message: "Processing file.ts", percentage: 25 }

// End  
{ kind: "end", message: "Indexing complete" }

Logging & Telemetry

window/logMessage: LogMessageParams     // Development logs
telemetry/event: any                   // Usage analytics

Version History

LSP 3.17 (Current)

Major new feature are: type hierarchy, inline values, inlay hints, notebook document support and a meta model that describes the 3.17 LSP version.

Key Features:

  • Type hierarchy support
  • Inline value provider
  • Inlay hints
  • Notebook document synchronization
  • Diagnostic pull model
  • Position encoding negotiation

LSP 3.16

Key Features:

  • Semantic tokens
  • Call hierarchy
  • Moniker support
  • File operation events
  • Linked editing ranges
  • Code action resolve

LSP 3.15

Key Features:

  • Progress reporting
  • Selection ranges
  • Signature help context

LSP 3.0

Breaking Changes:

  • Client capabilities system
  • Dynamic registration
  • Workspace folders
  • Document link support