Skip to content

v0.2.0 Design: ChatGPT Read MCP + ActualCoder

Historical v0.2 design document. It records an earlier product boundary and is not the current v0.5.0 capability matrix. See Architecture, Security, and v0.5.0 release notes for current behavior.

Product goal

v0.2 keeps the normal ChatGPT integration read-only on personal Pro, and adds ActualCoder, an agent-neutral local coding layer that can hand managed worktrees to Codex CLI, GitHub Copilot CLI, or future coding agents.

The GitLab/worktree machinery is deliberately separated from the coding model/backend. This gives the maximum practical capability without requiring a Business workspace or OpenAI model API billing from this project.

Normal ChatGPT Pro
    │
    ├── read/search GitLab via MCP
    ├── architecture review
    ├── MR / CI diagnosis
    └── define the coding task
             │
             ▼
Codex CLI ───────┐
Copilot CLI ─────┼──► ActualCoder
future agents ───┘        │
                          ▼
                    gitlab-agent
                          │
                          ├── create/recover isolated worktree
                          ├── read/write/apply patch
                          ├── run allowlisted build/tests
                          ├── inspect diff
                          ├── commit
                          ├── push / push-update
                          └── create/recover GitLab MR workflow

Hard requirement: zero OpenAI model API usage

The v0.2 project code must not call OpenAI model APIs.

Specifically:

  • no openai Python dependency;
  • no calls to /v1/responses, Chat Completions, embeddings, image APIs, etc.;
  • the local coding engine does not require OPENAI_API_KEY;
  • Codex should be signed in with the user's ChatGPT subscription rather than an API key when avoiding API billing;
  • CONTROL_PLANE_API_KEY remains a tunnel runtime credential used by tunnel-client, not by gitlab-agent.

CI checks project code for accidental OpenAI model API usage.

v0.2 components

1. Existing read-only MCP

server.py remains the ChatGPT-facing read MCP.

It exposes:

  • repository/file reads;
  • project code search;
  • merge request metadata/diffs;
  • pipelines/jobs/logs.

No MCP write tools are added for the personal-Pro workflow.

2. ActualCoder + local package gitlab_agent

The package under src/gitlab_agent/ contains:

  • config.py — local settings and safety policy;
  • workspace.py — repository cache, worktrees, file edits, Git commit/push/MR flow;
  • runner.py — structured build/test command runner;
  • cli.py — shared JSON-oriented command implementation;
  • actual-coder_cli.py — agent-neutral actual-coder entry point.

Installed commands:

actual-coder
gitlab-agent

actual-coder is the user-facing orchestration layer. gitlab-agent remains the lower-level GitLab/worktree lifecycle controller.

Workspace model

Default root:

~/.local/share/chatgpt-gitlab-mcp/
├── repos/
│   └── <project-key>.git
├── worktrees/
│   └── <workspace-id>/
├── state/
│   └── <workspace-id>.json
└── runner-home/
    └── <workspace-id>/

A task gets a unique branch such as:

chatgpt/fix-timeout-a1b2c3d4

The user's normal checkout is never modified.

Safety rules

Project boundary

By default, v0.2 refuses to create a coding workspace unless:

GITLAB_ALLOWED_PROJECTS=team/project-a,team/project-b

is configured.

This can be disabled only by explicitly setting:

GITLAB_REQUIRE_WRITE_ALLOWLIST=false

Branch boundary

Generated coding branches must start with:

chatgpt/

or the configured GITLAB_BRANCH_PREFIX.

The engine never force-pushes and never pushes directly to the base branch.

File boundary

All file reads/writes are resolved against the managed worktree. Absolute paths and paths escaping the worktree are rejected.

Command boundary

Build/test execution:

  • uses subprocess with shell=False;
  • requires a bare executable name;
  • uses an executable allowlist;
  • fixes the working directory to the selected worktree;
  • enforces a timeout;
  • caps stdout/stderr;
  • removes token/secret/password/API-key environment variables;
  • removes SSH_AUTH_SOCK;
  • uses an isolated HOME directory.

Default executable allowlist:

python, python3, pytest, uv,
node, npm, pnpm, yarn,
make, cmake, ninja,
cargo, go, mvn, gradle

Not allowed by default:

bash, sh, zsh, curl, ssh, sudo, git

Git is exposed through dedicated higher-level operations instead.

Important: this host runner is not a filesystem/container sandbox. A malicious build script can still attempt to access files on the host by absolute path. Run untrusted repositories in a container/VM.

Git credentials

The read MCP keeps:

GITLAB_TOKEN

with recommended scopes:

read_api
read_repository

The local Git engine optionally uses a separate:

GITLAB_GIT_TOKEN

For clone-only operation, read_repository is sufficient.

For push / MR flow, use:

write_repository

If GITLAB_GIT_TOKEN is unset, the CLI falls back to GITLAB_TOKEN.

The token is provided to Git through a temporary GIT_ASKPASS helper and is not stored in the remote URL.

Merge Request creation without broad API scope

v0.2 can create an MR on the branch's first push using GitLab push options:

merge_request.create
merge_request.target=<branch>
merge_request.title=<title>
merge_request.description=<description>

This lets the initial workflow use Git-over-HTTP write_repository rather than a broad GitLab api scope.

The push-mr command is intentionally intended for the first push of the feature branch. If you already pushed the branch with gitlab-agent push, create the MR manually or use a later API-based extension with separately reviewed permissions.

CLI workflow

Create a workspace:

gitlab-agent create team/project-a \
  --base-ref main \
  --task fix-timeout

Inspect:

gitlab-agent status <workspace-id>
gitlab-agent read <workspace-id> src/example.py
gitlab-agent diff <workspace-id>

Apply a patch:

cat change.patch | gitlab-agent apply-patch <workspace-id>

Or replace a file:

cat new_file.py | gitlab-agent write <workspace-id> src/new_file.py

Run an allowlisted test/build command:

gitlab-agent run <workspace-id> -- uv run pytest

Commit:

gitlab-agent commit <workspace-id> -m "Fix timeout handling"

Push and create MR:

gitlab-agent push-mr <workspace-id> \
  --target main \
  --title "Fix timeout handling" \
  --description-file mr.md

All commands return JSON so Codex can consume the result reliably.

ActualCoder backend interaction

Create a Codex handoff:

actual-coder task team/project-a --agent codex --task fix-timeout --goal "Fix the timeout bug"

Or hand the same workflow to GitHub Copilot CLI:

actual-coder task team/project-a --agent copilot --task fix-timeout --goal "Fix the timeout bug"

Coding backends may inspect/edit the returned worktree directly. ActualCoder and gitlab-agent provide a consistent lifecycle, project allowlist, branch policy, test/diff review path, safe push/MR creation, MR iteration, and MR/branch recovery.

Deliberately deferred

v0.2 does not implement:

  • merge MR;
  • approve MR;
  • force push;
  • delete remote branches;
  • direct push to default branch;
  • CI/CD variable writes;
  • secret management;
  • arbitrary shell;
  • production deployment actions.

Future path

If personal ChatGPT later supports custom MCP write actions, the same WorkspaceManager and CommandRunner can be exposed as MCP tools without redesigning the backend.