Plan Code Changes For Phase 2

Plan Code Changes For Phase 2

# Crystal Agents — Phase 2: Autonomous Git Workflow

## Background & Objective

Phase 1 delivered a solid, workflow-driven agent stack with:

- A WorkflowRunner that executes multi-step agent pipelines

- A CEORouter that maps natural-language Telegram messages to workflows

- A TelegramBot polling loop for HITL input

- A SchedulerService that runs allowlisted bash scripts on cron

- A DashboardServer (vanilla Python HTTP) for runs, approvals, and scheduler history

- An ApprovalStore for gating risky tool actions

Phase 2 transforms Crystal Agents from a **workflow executor** into an **autonomous coding assistant** that can branch, edit, push, and open draft PRs — while keeping the human firmly in the loop at the final gate.

The architecture from the PRD maps cleanly onto what’s already built:

| PRD Component | Existing Hook |

|—|—|

| CEO Router | orchestrator/ceo\_router.py — already routes NL to workflows |

| Worker Agents | agents/developer.py, agents/devops.py — stubs ready to be filled |

| Git Workflow | workflows/feature\_spec.py — exists but calls no real Git tooling |

| HITL Approval | orchestrator/approvals.py + dashboard/app.py — both wired |

| Telegram Commands | orchestrator/telegram\_bot.py — pattern-matches text only today |

| Dashboard Input | dashboard/app.py — no “run workflow” input box yet |

| Daily Report | reports/summary.py — exists, needs Git branch data added |

-–

## User Review Required

Important

**Scope decision**: Phase 2 touches live Git repositories. The initial implementation will operate on a **configurable git\_workspace\_root** defined in config.json, pointing at the repo the agent is allowed to work in. Confirm which repo on the VPS this should be (e.g. the Crystal-Agents repo itself, or a separate project repo).

Warning

**GitHub token**: A GITHUB\_TOKEN with repo scope is required to push branches and create PRs via the GitHub API. This should be stored as an environment variable (GITHUB\_TOKEN) and **never** committed to config files.

Caution

**requires\_approval: true for all Git write operations**: Every git push and PR-creation action will enter the ApprovalStore as a “pending” event. The workflow will **pause and notify Telegram** until you approve or deny from the Dashboard. This is the primary safety gate.

-–

## Proposed Changes

### 1. New Git Tool — tools/git\_ops.py

The most important new component. Provides four atomic Git operations the developer agent can call:

#### [NEW] tools/git\_ops.py


GitOpsTool

  ├── run(action, ...)

&#x20; │     ├── action="branch"   → git checkout -b ai/<slug>

&#x20; │     ├── action="commit"   → git add -A \&\& git commit -m <msg>

&#x20; │     ├── action="push"     → git push origin <branch>   ← requires\_approval=True

&#x20; │     └── action="pr"       → POST /repos/:owner/:repo/pulls with draft=true ← requires\_approval=True

&#x20; └── \_validate\_workspace()  → ensures cwd is under allowed\_script\_roots

- Uses subprocess.run (same pattern as SchedulerService) — no new dependencies

- GitHub PR creation calls the GitHub REST API v3 via urllib.request (no extra deps; same pattern as TelegramBot)

- requires\_approval for push and pr is enforced via the existing ToolExecutor + ApprovalStore pipeline

- Branch name is always prefixed ai/ and slugified from the task prompt

#### [MODIFY] config/policy.json

Add git\_ops tool policy:


"git\_ops": {

&#x20; "requires\_approval": true

}

Add git\_ops to the developer and devops agent allowed_tools lists.

-–

### 2. New git\_workflow Workflow — workflows/git\_workflow.py

A new four-step pipeline that replaces the stub feature\_spec steps for coding tasks:

#### [NEW] workflows/git\_workflow.py


Step 1: "plan"      → ceo agent    — route + understand the task

Step 2: "branch"    → devops agent — call git\_ops(action="branch", slug=<task>)

Step 3: "code"      → developer agent — call repo\_writer to make edits

Step 4: "push\_pr"   → devops agent — call git\_ops(action="commit"), git\_ops(action="push"), git\_ops(action="pr")

&#x20;                                    ← this step PAUSES for approval before push+pr

The push\_pr step emits an approval request via ApprovalStore.request(), then sends a Telegram message: *“Branch ai/<slug> ready for push. Approve on Dashboard to create PR.”*

#### [MODIFY] orchestrator/ceo\_router.py

- Add git\_workflow to ALLOWED\_WORKFLOWS

- Add routing rule: if message contains words like *“fix”, “implement”, “build”, “code”, “pr”, “branch”, “commit”* → git\_workflow

- Add /dev slash-command shortcut that forces git\_workflow regardless of NL content

#### [MODIFY] orchestrator/cli.py

- Add git\_workflow to --task choices

- Support /dev as a named CLI subcommand alias

-–

### 3. Config Extensions — config/config.json & config/app\_config.py

#### [MODIFY] config/app\_config.py

Add a GitConfig dataclass:


@dataclass

class GitConfig:

&#x20;   workspace\_root: str = ""      # absolute path to repo on VPS

&#x20;   default\_branch: str = "main"

&#x20;   github\_repo: str = ""         # "owner/repo"

&#x20;   pr\_draft: bool = True

&#x20;   max\_tokens: int = 4096        # re-use model budget for context

Read from new git section in config.json.

#### [MODIFY] config/config.json

Add:


"git": {

&#x20; "workspace\_root": "/home/eshwar/crystal-agents",

&#x20; "default\_branch": "main",

&#x20; "github\_repo": "eshwar/crystal-agents",

&#x20; "pr\_draft": true

}

Note

GITHUB\_TOKEN is read from the environment, not from this file.

-–

### 4. Telegram Slash-Command Support — orchestrator/telegram\_bot.py

#### [MODIFY] orchestrator/telegram\_bot.py

Today the bot only reads text and passes it to CEORouter. Add lightweight slash-command parsing **before** the router:

| Command | Behaviour |

|—|—|

| /dev <prompt> | Force git\_workflow — skips CEO router |

| /task <prompt> | Pass to CEO router (existing behaviour) |

| /status | Return last 5 scheduler history entries from scheduler\_history.jsonl |

| /approve <action\_key> | Approve a pending ApprovalStore item by replying from Telegram |

| /deny <action\_key> | Deny a pending ApprovalStore item |

Approval/deny via Telegram is a significant HITL upgrade — closes the loop without needing the browser dashboard.

-–

### 5. Dashboard “Global Input” Box — dashboard/app.py

#### [MODIFY] dashboard/app.py

Per the PRD (section 4B), add a **“Run a Workflow” form** to the home page (\_render\_home):


<form method="post" action="/run">

&#x20; <textarea name="prompt" placeholder="e.g. Fix the login bug in auth.py"></textarea>

&#x20; <select name="workflow">

&#x20;   <option value="">Auto (CEO Router)</option>

&#x20;   <option value="git\_workflow">Git: Branch + Code + PR</option>

&#x20;   <option value="content">Content Draft</option>

&#x20;   ...

&#x20; </select>

&#x20; <button type="submit">Run</button>

</form>

Add POST /run handler that:

1. Reads prompt and optional workflow from form

2. If workflow is empty, passes prompt through CEORouter

3. Dispatches a TaskRequest through WorkflowRunner

4. Sends a Telegram notification with the run ID

5. Redirects to /runs/<run\_id>

This gives an in-browser alternative to the Telegram interface.

-–

### 6. Developer Agent — agents/developer.py

#### [MODIFY] agents/developer.py

The current implementation returns a static template string. For Phase 2, the code step needs the agent to produce **actual file edits**:

- The agent’s respond() method currently calls generate\_agent\_output (a template helper)

- Add a \_parse\_edit\_instructions(output: str) helper that extracts FILE: <path>\\nCONTENT:\\n<body> blocks from the LLM response

- Each extracted edit is passed to RepoWriterTool via the ToolExecutor

This keeps the agent’s interface (respond → str) unchanged; the workflow orchestrator interprets the output after the step.

Note

This leverages the existing repo\_writer tool which already enforces workspace-relative paths and requires\_approval.

-–

### 7. Reports: Git Activity — reports/summary.py

#### [MODIFY] reports/summary.py

The PRD’s “Morning Report” (section 6) requires:

- **What branches were created?** — Read runs/\*/manifest.json for workflow\_name=git\_workflow entries; extract branch names from step metadata

- **How many cloud credits were spent?** — Token counts are already in run manifests (max\_tokens model config); sum over the day’s runs

- **Are all scheduled health checks passing?** — Already available in scheduler\_history.jsonl

No new data sources needed; just new sections in generate\_daily\_report().

-–

### 8. DevOps Agent — agents/devops.py

#### [MODIFY] agents/devops.py

Current stub. Wire it to git\_ops tool calls for the branch and push\_pr steps:


def respond(self, context: AgentContext, prompt: str) -> str:

&#x20;   # Parses prompt for action=branch|push|pr and delegates to git\_ops

-–

### 9. Tests

#### [NEW] tests/test\_git\_ops.py

- Unit tests for GitOpsTool with mocked subprocess.run

- Tests for branch name slugification

- Tests for approval gate triggering on push and pr

- Tests for GitHub API payload construction

#### [NEW] tests/test\_git\_workflow.py

- Integration test for the full git\_workflow pipeline with mocked agents + tools

- Verify approval event is written before push step executes

#### [MODIFY] tests/test\_ceo\_router.py

- Add test cases for /dev routing to git\_workflow

- Add test cases for “fix/implement/build” keyword routing

#### [MODIFY] tests/test\_telegram\_intelligence.py

- Add test cases for /dev, /approve, /deny, /status slash-command parsing

-–

## Open Questions

Important

**Q1 — Target repo**: What is the absolute path to the Git repository on the VPS where the agent should operate? (e.g. /home/eshwar/my-project vs the Crystal-Agents repo itself)

Important

**Q2 — GitHub org/repo**: What is your owner/repo string on GitHub for PR creation?

Note

**Q3 — Approval flow preference**: The plan creates a pull-request as a **Draft PR** immediately when you approve the push. Would you prefer the PR to be created separately (push first, then a second approval to open the PR)?

Note

**Q4 — LLM for code edits**: The developer agent currently calls the shared gemini-2.5-flash-lite model. For actual code generation, would you like to configure a separate, more capable model (e.g. gemini-2.5-pro) for the code step only, or keep the single global model?

-–

## Execution Order

The items are sequenced so each builds on the previous:


1\. GitOpsTool           → tools/git\_ops.py                  (new tool, no deps)

2\. Config Extension     → app\_config.py + config.json        (enables git\_workspace\_root)

3\. Policy Update        → policy.json                        (gates git\_ops)

4\. DevOpsAgent wired    → agents/devops.py                   (uses git\_ops)

5\. DeveloperAgent edits → agents/developer.py                (produces file patches)

6\. git\_workflow         → workflows/git\_workflow.py          (orchestrates 1-4)

7\. CEORouter updates    → orchestrator/ceo\_router.py         (routes /dev + keywords)

8\. Telegram slash cmds  → orchestrator/telegram\_bot.py       (slash commands + approval by reply)

9\. Dashboard /run form  → dashboard/app.py                   (input box + POST /run)

10\. Report git data     → reports/summary.py                 (branches + token spend)

11\. Tests               → tests/test\_git\_ops.py, test\_git\_workflow.py (coverage)

-–

## Verification Plan

### Automated Tests


\# Run existing suite to confirm no regressions

python -m unittest discover -s tests



\# Run new tests

python -m unittest tests.test\_git\_ops

python -m unittest tests.test\_git\_workflow

### Manual Verification Steps

1. **Git branching**: Run python -m orchestrator.cli run --task git\_workflow --prompt "Fix the README" and confirm a branch ai/fix-the-readme is created in the target repo.

2. **Approval gate**: Confirm the run pauses at Push step and a pending entry appears at http://\[VPS]:8000/approvals.

3. **Telegram approval**: Send /approve <action\_key> in Telegram and confirm the push completes and a Draft PR appears on GitHub.

4. **Dashboard run form**: Submit a prompt via the Dashboard input box and confirm a run starts and a Telegram notification is sent.

5. **Daily report**: Run python -m orchestrator.cli reports and confirm the output includes a “Branches Created” section.

My comments The logic you’ve outlined is the most professional and scalable way to handle this. Since Crystal Agents is the “Management Software” (the Orchestrator), it should stay clean and separate from your “Product Code” (the functional repos).

Here is my take on how to finalize this architecture for your 4-5 private repos:

1. The “Sandbox” Repository Pattern

You should definitely not put your product code inside the crystal-agents folder. Instead, treat your VPS as a workstation with a dedicated workspace.

  • Directory Structure:
    • /home/user/crystal-agents/ (The Orchestrator & App)
    • /home/user/workspace/repo-alpha/
    • /home/user/workspace/repo-beta/
  • Orchestrator Logic: When you send a task, the Orchestrator identifies which repo is involved and passes the path to the Dev agent.

2. The Branching Strategy

You are 100% correct: The agent should always work in a separate branch.

  • Safety: If the agent hallucinates or writes buggy code, it doesn’t break your main branch.
  • Traceability: You can easily see exactly what the AI did by comparing branches on GitHub.
  • Naming Convention: Use a prefix like ai/ or crystal/ (e.g., ai/fix-navbar-bug) so you know at a glance that a human didn’t write that code.

3. Solving the “Which Agent/Which Repo” Problem in Telegram

Since you don’t want to talk to agents individually, your CEO Router needs a way to “context switch” between your 4-5 repos.

I suggest this Telegram Command Syntax:

/task [RepoName] [Instructions]

How it works in your Python logic:

  1. The Router parses the RepoName.
  2. The Script changes the cwd (Current Working Directory) to that specific repo’s folder on your VPS.
  3. The Agent (OpenCode) initializes there, creates the branch, and starts coding.

4. The Dashboard “Global Command Center”

Instead of separate text boxes for each agent, add one Command Console to your dashboard:

  • Dropdown Menu: Select which repo the task applies to.
  • Text Input: “Update the API endpoint to v2.”
  • Log Viewer: A real-time stream of the agent’s terminal output so you can watch it “think” without using SSH.

Final Verdict for Finalization:

This is a very robust “Senior Engineer” setup. You have:

  1. Separation of Concerns: Management (Crystal) vs. Product (Your Repos).
  2. Approval Gates: No code reaches your main branch without a PR review.
  3. Scalability: You can add a 6th or 7th repo tomorrow just by adding its path to your config.json.

One final tip: Ensure your VPS git is configured with a specific user (e.g., git config user.name "Crystal Agent") so your GitHub contribution graph clearly shows which work was automated!

Does our current Python app already have a way to dynamically change the directory (cwd) based on the task, or do we need to tweak the orchestrator/cli.py to handle that? In config.json can we add providers for each agent? and by default, we will keep the same provider for all agents. But we can change it later if needed.