Design Document

Dev Console — Product Design Document#

1. Overview#

Dev Console is an AI-chat-first development environment designed for software engineers who want to develop software from mobile devices or thin web clients while the heavy lifting runs on self-managed Linux infrastructure. The system organises work around three nested resources: a project (a repository), one or more workspaces (branch-scoped instances of that project), and agent sessions that act inside a workspace through chat. Developers can review and accept the changes agents propose, browse files, and interact with a terminal, all from a lightweight browser or mobile client.


2. Goals#

#Goal
G1Run entirely on self-managed Linux servers; no dependency on third-party cloud execution environments.
G2Provide a copilot-style AI chat interface as the primary way to make code changes.
G3Expose a terminal that is scoped to the active workspace.
G4Let users view, browse, and manually edit files in the workspace.
G5Present diffs of agent-proposed changes and let the user accept or reject them.
G6Authenticate users exclusively through GitHub OAuth (no separate credential store).
G7Keep the client thin enough to run well on mobile browsers.

Non-Goals#


3. Architecture Overview#

┌──────────────────────────────────────────────────────────────┐
│                        Client Layer                          │
│  ┌────────────────────┐    ┌────────────────────────────┐    │
│  │   Web App (SPA)    │    │   Mobile Browser / PWA     │    │
│  └────────┬───────────┘    └────────────┬───────────────┘    │
└───────────┼────────────────────────────┼────────────────────┘
            │  HTTPS / WebSocket         │
┌───────────▼────────────────────────────▼────────────────────┐
│                     Dev Console Server                       │
│  ┌──────────────┐  ┌───────────────┐  ┌──────────────────┐  │
│  │  Auth Module │  │  REST/WS API  │  │  Static Asset    │  │
│  │ (GitHub OAuth│  │ (HTTPS + WS)  │  │   Serving        │  │
│  └──────────────┘  └───────┬───────┘  └──────────────────┘  │
│                            │                                 │
│  ┌─────────────────────────▼─────────────────────────────┐  │
│  │                    Core Services                       │  │
│  │  ┌─────────────┐  ┌──────────────┐  ┌─────────────┐   │  │
│  │  │  Project /  │  │  Agent Chat  │  │  Terminal   │   │  │
│  │  │  Workspace  │  │  Manager     │  │  Manager    │   │  │
│  │  │  Manager    │  └──────┬───────┘  └──────┬──────┘   │  │
│  │  └──────┬──────┘         │                 │          │  │
│  └─────────┼────────────────┼─────────────────┼──────────┘  │
│            │                │                 │              │
│  ┌─────────▼────────────────▼─────────────────▼──────────┐  │
│  │               Infrastructure Layer                     │  │
│  │  ┌───────────┐  ┌──────────────┐  ┌───────────────┐   │  │
│  │  │ File I/O  │  │  LLM Client  │  │  PTY / Shell  │   │  │
│  │  │ (fs, git) │  │ (HTTP client)│  │  (pty/exec)   │   │  │
│  │  └───────────┘  └──────────────┘  └───────────────┘   │  │
│  └────────────────────────────────────────────────────────┘  │
└──────────────────────────────────────────────────────────────┘

The server is a single long-running process written in Go (see §9). It hosts the REST + WebSocket API and serves the compiled client SPA as static assets from the same origin, removing CORS complexity.


4. Authentication#

4.1 GitHub OAuth Flow#

Client                Server                  GitHub
  │                     │                       │
  │── GET /auth/login ──▶│                       │
  │◀─ 302 → github.com ─│                       │
  │                     │                       │
  │── GET /auth/callback?code=… ────────────────▶│
  │                     │◀── access_token ───────│
  │                     │── GET /user ───────────▶│
  │                     │◀── { login, id } ───────│
  │                     │                       │
  │◀─ Set-Cookie: session ─│                    │
  1. The server is configured with a GitHub OAuth App client_id / client_secret.
  2. /auth/login redirects to GitHub’s authorization page with scope=read:user.
  3. GitHub redirects back to /auth/callback; the server exchanges the code for an access token, fetches the user’s GitHub login and numeric ID, then creates a signed, HTTP-only session cookie (JWT or opaque token backed by an in-memory or on-disk store).
  4. All subsequent API calls must present this session cookie. The server validates it on every request.
  5. An allowlist of GitHub user IDs (configured by the operator) controls who may access the server.

4.2 Session Management#


5. Projects#

A project is the top-level context resource. All work happens within a project, and each project corresponds 1:1 with a single Git repository. Projects are created dynamically by authenticated users through the UI — users select a GitHub repository and the server registers and clones it into a configurable local storage directory.

5.1 Data Model#

Project {
  id:        string    // URL-safe slug derived from repo name (lowercased,
                       // non-alphanumeric characters replaced with hyphens);
                       // a numeric suffix is appended on collision
  name:      string    // Display name (defaults to repo name)
  repoURL:   string    // GitHub repository URL, e.g. "https://github.com/owner/repo"
  rootPath:  string    // Absolute path on the server where the repo is cloned
  createdAt: timestamp
}

5.2 Operations#

OperationDescription
GET /api/projectsList all projects.
POST /api/projectsCreate a new project by providing a GitHub repository URL. Server clones the repo into storage.projectsDir.
GET /api/projects/:pidGet project metadata.
DELETE /api/projects/:pidDelete a project and its on-disk clone.

To support repository selection in the UI, the following GitHub-proxy endpoint is also provided:

OperationDescription
GET /api/github/reposList GitHub repositories accessible to the authenticated user (proxied from the GitHub API using the user’s OAuth token).

6. Workspaces#

A workspace is an instance of a project. Workspaces are 1:1 with a Git branch and (optionally) a pull request. A project may have many workspaces (e.g. one per feature branch or PR under review).

6.1 Data Model#

Workspace {
  id:        string    // URL-safe slug, e.g. "feature-auth"
  projectId: string    // Parent project
  name:      string    // Display name (defaults to branch name)
  branch:    string    // Git branch name
  prNumber:  int?      // GitHub PR number (null until a PR is opened)
  createdAt: timestamp
}

6.2 Operations#

OperationDescription
GET /api/projects/:pid/workspacesList workspaces for a project.
POST /api/projects/:pid/workspacesCreate a new workspace (checks out the branch).
GET /api/projects/:pid/workspaces/:widGet workspace metadata.
DELETE /api/projects/:pid/workspaces/:widDelete a workspace.
GET /api/projects/:pid/workspaces/:wid/files?path=List directory contents at path (relative to workspace root).
GET /api/projects/:pid/workspaces/:wid/file?path=Read file contents.
PUT /api/projects/:pid/workspaces/:wid/file?path=Write file contents (manual edit).
GET /api/projects/:pid/workspaces/:wid/git/statusReturn git status for the workspace.
GET /api/projects/:pid/workspaces/:wid/git/diff?path=Return unified diff for a path.

6.3 File Browsing#

The client displays a file tree for the workspace root. Directories are expanded on demand via GET /api/projects/:pid/workspaces/:wid/files?path=<dir>. File contents are fetched and rendered in a read-only viewer with syntax highlighting. A separate “edit” mode switches to a minimal text editor.


7. Agent Chat Sessions#

An agent session is a conversation between the user and an AI copilot that can read and write files inside the workspace. Agent sessions live within a workspace; a workspace may have any number of agent sessions.

7.1 Data Model#

AgentSession {
  id:          string
  workspaceId: string
  projectId:   string
  title:       string       // Auto-generated from first user message
  createdAt:   timestamp
  updatedAt:   timestamp
  status:      "active" | "idle" | "error"
}

Message {
  id:        string
  sessionId: string
  role:      "user" | "assistant" | "tool"
  content:   string       // Markdown text or tool-call JSON
  createdAt: timestamp
}

PendingChange {
  id:        string
  sessionId: string
  filePath:  string       // Relative to workspace root
  diff:      string       // Unified diff
  status:    "pending" | "accepted" | "rejected"
  createdAt: timestamp
}

7.2 Operations#

OperationDescription
GET /api/projects/:pid/workspaces/:wid/sessionsList agent sessions for a workspace.
POST /api/projects/:pid/workspaces/:wid/sessionsCreate a new agent session.
DELETE /api/projects/:pid/workspaces/:wid/sessions/:sidClose/delete a session.
GET /api/projects/:pid/workspaces/:wid/sessions/:sid/messagesFetch message history.
WS /api/projects/:pid/workspaces/:wid/sessions/:sid/chatBidirectional stream for chat messages and agent events.
GET /api/projects/:pid/workspaces/:wid/sessions/:sid/changesList pending changes proposed by the agent.
POST /api/projects/:pid/workspaces/:wid/sessions/:sid/changes/:cid/acceptApply a pending change to the workspace.
POST /api/projects/:pid/workspaces/:wid/sessions/:sid/changes/:cid/rejectDiscard a pending change.

7.3 WebSocket Chat Protocol#

Messages over the WebSocket are JSON-encoded and follow this envelope:

// Client → Server
{ "type": "user_message", "content": "Refactor the auth module to use JWT" }
{ "type": "cancel" }          // Interrupt the in-flight agent turn

// Server → Client
{ "type": "assistant_chunk",  "content": "Sure, I'll start by…" }
{ "type": "assistant_done" }
{ "type": "tool_call",        "name": "read_file",   "args": { "path": "auth.go" } }
{ "type": "tool_result",      "name": "read_file",   "content": "…file contents…" }
{ "type": "change_proposed",  "changeId": "c1",      "filePath": "auth.go", "diff": "…" }
{ "type": "error",            "message": "LLM API quota exceeded" }

7.4 Agent Tool Set#

The agent is given the following tools by the server (function-calling style):

ToolDescription
read_file(path)Read a file from the workspace.
write_file(path, content)Propose a file change (creates a PendingChange; does not write to disk until accepted).
list_files(path)List directory contents.
run_command(cmd, args)Execute a shell command inside the workspace directory and return stdout/stderr. Restricted to a configurable allowlist (e.g. go test, npm test, make).
git_diff(path?)Return the current git diff.

8. Terminal#

The terminal provides a full PTY session inside the workspace directory.

8.1 Operations#

OperationDescription
POST /api/projects/:pid/workspaces/:wid/terminalsCreate a new terminal session; returns { terminalId }.
DELETE /api/projects/:pid/workspaces/:wid/terminals/:tidKill the terminal.
WS /api/projects/:pid/workspaces/:wid/terminals/:tidAttach stdin/stdout/stderr over WebSocket.

8.2 WebSocket Terminal Protocol#

Raw PTY bytes are forwarded over the WebSocket. The client sends resize events as a JSON message before switching to binary mode:

// Client → Server (JSON control message, UTF-8 text frame)
{ "type": "resize", "cols": 220, "rows": 50 }

// After the handshake, all frames are binary (raw PTY bytes).
// Client → Server: stdin bytes
// Server  Client: stdout/stderr bytes

8.3 Security#


9. Change Review#

When an agent proposes a file change it is stored as a PendingChange. The user reviews proposed diffs in the client before they are written to disk.

9.1 Review UI Flow#

  1. After an agent turn completes, the client shows a badge on each changed file in the file tree.
  2. Selecting a changed file opens a side-by-side or unified diff view.
  3. The user clicks Accept or Reject per file.
  4. Accepting calls POST …/changes/:cid/accept; the server atomically writes the new file content and records the change as accepted.
  5. Rejecting calls POST …/changes/:cid/reject; the pending change is discarded and the file is left unmodified.
  6. After all changes in a session are resolved the agent session returns to idle status.

10. Technology Stack#

10.1 Server#

ConcernChoiceRationale
LanguageGoSingle static binary; good concurrency; easy deployment.
HTTP frameworknet/http + gorilla/mux or chiLightweight; no magic.
WebSocketgorilla/websocketDe facto standard in the Go ecosystem.
PTYcreack/ptyMature Go PTY library.
LLM clientOpenAI-compatible HTTP clientWorks with OpenAI, Anthropic (via compatibility layer), or local models (Ollama).
Session storeJWT (golang-jwt/jwt) signed with operator secretStateless; no database required.
ConfigurationYAML file + env variable overridesSimple operator experience.

10.2 Client#

ConcernChoiceRationale
LanguageTypeScriptStrong typing; large ecosystem.
FrameworkReact (Vite build)Widely known; fast iteration; good mobile support.
Terminal emulatorxterm.jsWidely used; handles full VT100/VT220 escapes.
Diff viewerreact-diff-viewer or similarRenders unified/side-by-side diffs.
Syntax highlightinghighlight.js or prism.jsClient-side, no server dependency.
StylingTailwind CSSResponsive by default; small bundle.
State managementReact Query + ZustandServer state + local UI state.

10.3 Deployment#

The operator runs a single dev-console binary and a short configuration file:

# dev-console.yaml
server:
  listenAddr: ":8080"
  tls:
    certFile: "/etc/dev-console/tls.crt"
    keyFile:  "/etc/dev-console/tls.key"

auth:
  github:
    clientId:     "Ov23liABCDEFGH"
    clientSecret: "${GITHUB_CLIENT_SECRET}"   # env variable
    callbackUrl:  "https://console.example.com/auth/callback"
  allowedGithubUsers:
    - "alice"
    - "bob"
  sessionSecret: "${SESSION_SECRET}"
  sessionTtl:    "24h"

llm:
  provider:    "openai"           # or "anthropic", "ollama"
  apiKey:      "${OPENAI_API_KEY}"
  model:       "gpt-4o"
  allowedCommands:
    - "go"
    - "npm"
    - "make"
    - "git"

storage:
  projectsDir: "/srv/projects"   # root directory where project repos are cloned

The compiled client SPA is embedded in the server binary using Go’s embed package so no separate static file server is needed.


11. Security Considerations#

ThreatMitigation
Unauthorized accessGitHub OAuth + allowlist of GitHub user IDs. All API routes require a valid session.
Session hijackingHTTP-only, Secure, SameSite=Strict cookies; short TTL with refresh.
Path traversal in file APIAll file paths are resolved against the workspace root and rejected if the resolved path escapes it.
Arbitrary command execution via terminalTerminal access is behind authentication; the operator controls which projects, workspaces, and shells are allowed.
Agent-triggered command executionrun_command tool is restricted to an operator-configured allowlist; commands run in a sandboxed subprocess with the workspace as the working directory.
LLM prompt injectionAgent tool outputs are escaped before being re-injected into the context; file content passed to the LLM is clearly delimited.
TLSThe server is expected to terminate TLS directly (cert/key configurable) or be placed behind a TLS-terminating reverse proxy.
Secret managementSecrets (OAuth client secret, session secret, LLM API key) are read from environment variables; they are never stored in the config file or logged.

12. Open Questions / Future Work#