Skip to content

Commit 17ff207

Browse files
authored
🤖 feat: include model + thinking in bash tool env (#1118)
Expose model + thinking level for PR attribution via bash env vars. - `getMuxEnv(...)` now supports optional `{ modelString, thinkingLevel }` - `AIService` threads the current model + effective thinking level into tool env as: - `MUX_MODEL_STRING` - `MUX_THINKING_LEVEL` - `docs/AGENTS.md` updated to require the new attribution footer format. Validation: - `bun test src/node/runtime/initHook.test.ts` - `make static-check` --- <details> <summary>📋 Implementation Plan</summary> # Plan: Extend GitHub PR attribution with model + thinking level ## Problem Today, PR bodies often end with a simple attribution footer like: - `_Generated with \`mux\`_` You want that attribution to also include: 1. The **model** used to generate the changes. 2. The **thinking level** used. ## Goals - Provide a **standard, copy/pasteable attribution footer format** that includes model + thinking level. - Make the **exact model + thinking level available to the agent at runtime** so the footer can be accurate (no guessing). - Keep changes minimal; avoid introducing a full GitHub integration feature unless required. ## Non-goals (for this iteration) - Automatically creating PRs from inside mux via a dedicated “Create PR” tool/UI. - Retroactively editing existing PRs server-side via GitHub Actions. ## Recommended output format Make the footer a single line (easy to scan) plus optional machine-readable comment (easy to parse later): ```md --- _Generated with `mux` • Model: `<modelString>` • Thinking: `<thinkingLevel>`_ <!-- mux-attribution: model=<modelString> thinking=<thinkingLevel> --> ``` Notes: - Use the **full mux `modelString`** (e.g. `openai:gpt-5.2-pro`) to avoid ambiguity. - `thinkingLevel` should be mux’s unified values: `off|low|medium|high|xhigh`. - No PR URL/number is included in the PR body attribution (it’s already on the PR). ## Approach options ### Approach A (recommended): Expose model/thinking context + update instructions (low risk) **Net LoC estimate (product code only): ~40–90 LoC** Implement: - Surface `modelString` + `thinkingLevel` in the **bash tool environment** as `MUX_MODEL_STRING` and `MUX_THINKING_LEVEL` so `gh pr ...` workflows can reference them. - Update repo `AGENTS.md` guidance to require the richer attribution footer. - **Do not** inject `thinkingLevel` into the system prompt (to avoid prompt-cache misses when switching e.g. `high` → `medium`). Why this is enough: - mux doesn’t currently own PR creation; agents typically run `gh` via the bash tool. - Env vars are available exactly where PR bodies are usually authored (shell workflows) without changing the LLM prompt/caching behavior. ### Approach B: Add a helper CLI/script for “create PR with mux attribution” **Net LoC estimate (product code only): ~0 LoC** (mostly script/docs), or **~200–400 LoC** if built into mux CLI/tools. Implement a script (e.g. `scripts/gh_pr_create_with_mux_attribution.ts`) that: 1. Creates the PR via `gh pr create ...`. 2. Ensures the PR body includes the attribution footer using `$MUX_MODEL_STRING` / `$MUX_THINKING_LEVEL`. This makes “add the correct footer” a single command, but is more opinionated and adds surface area. ## Detailed implementation plan (Approach A) ### 1) Preserve prompt caching behavior - **Do not** add per-request dynamic metadata (especially `thinkingLevel`) into the system prompt. - Prompt caching (e.g. Anthropic prompt caching) keys off the input; changing the system prompt when switching `high` → `medium` would reduce cache hits. - Therefore, this change intentionally avoids modifying `buildSystemMessage(...)`. ### 2) Add env vars for bash-based PR flows - Extend `getMuxEnv(...)` to optionally accept `{ modelString, thinkingLevel }`. - Populate: - `MUX_MODEL_STRING=<modelString>` - `MUX_THINKING_LEVEL=<thinkingLevel||off>` Files: - `src/node/runtime/initHook.ts` (extend `getMuxEnv` signature + implementation) - `src/node/services/aiService.ts` (pass model/thinking into `getMuxEnv` for tool calls) Notes: - Keep existing callers intact by making the new argument optional. - It’s OK that init hooks (which aren’t tied to a model invocation) won’t always set these vars. ### 3) Update AGENTS.md guidance to require the richer footer - Replace the existing instruction about `_Generated with \`mux\`_` with the new required template. - Add a short note: - model/thinking values should be sourced from `$MUX_MODEL_STRING` and `$MUX_THINKING_LEVEL` in bash (preferred; doesn’t affect prompt caching). Files: - `AGENTS.md` - `docs/AGENTS.md` (keep in sync if both are published) ### 4) Tests Add/update unit tests so this doesn’t regress: - Add/extend a `getMuxEnv` unit test (either new or in an existing runtime test file) - `getMuxEnv` includes the new env vars when options are provided. - Existing env vars remain unchanged. ## Validation steps (manual) - In a bash tool call (or any `gh` workflow), run: - `echo "$MUX_MODEL_STRING"` - `echo "$MUX_THINKING_LEVEL"` - Create/update a PR body footer and confirm it renders as expected. - Switch thinking level (e.g. `high` → `medium`) and confirm prompt caching behavior is unchanged (since the system prompt isn’t modified by this feature). ## Decisions (confirmed) - Footer includes **only** model + thinking level (no PR URL/number). - Footer label uses `Thinking:` (aligns with mux naming) and records the mux `thinkingLevel` value (`off|low|medium|high|xhigh`). - Include the hidden HTML comment for machine parsing. - Use the **current** modelString/thinkingLevel at PR creation/update time (no aggregation across sessions). </details> --- _Generated with `mux` • Model: `openai:gpt-5.2` • Thinking: `high`_ <!-- mux-attribution: model=openai:gpt-5.2 thinking=high --> Signed-off-by: Thomas Kosiewski <tk@coder.com>
1 parent 11681d2 commit 17ff207

File tree

4 files changed

+72
-5
lines changed

4 files changed

+72
-5
lines changed

docs/AGENTS.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,15 @@ description: Agent instructions for AI assistants working on the mux codebase
99

1010
- `mux`: Electron + React desktop app for parallel agent workflows; UX must be fast, responsive, predictable.
1111
- Minor breaking changes are expected, but critical flows must allow upgrade↔downgrade without friction; skip migrations when breakage is tightly scoped.
12-
- Public work (issues/PRs/commits) must use 🤖 in the title and include "_Generated with `mux`_" in the body when applicable.
12+
- Public work (issues/PRs/commits) must use 🤖 in the title and include this footer in the body:
13+
14+
```md
15+
---
16+
_Generated with `mux` • Model: `<modelString>` • Thinking: `<thinkingLevel>`_
17+
<!-- mux-attribution: model=<modelString> thinking=<thinkingLevel> -->
18+
```
19+
20+
Prefer sourcing values from `$MUX_MODEL_STRING` and `$MUX_THINKING_LEVEL` (bash tool env).
1321

1422
## PR + Release Workflow
1523

src/node/runtime/initHook.test.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { describe, it, expect } from "bun:test";
2-
import { LineBuffer, createLineBufferedLoggers } from "./initHook";
2+
import { LineBuffer, createLineBufferedLoggers, getMuxEnv } from "./initHook";
33
import type { InitLogger } from "./Runtime";
44

55
describe("LineBuffer", () => {
@@ -53,6 +53,7 @@ describe("LineBuffer", () => {
5353
});
5454
});
5555

56+
// getMuxEnv tests are placed here because initHook.ts owns the implementation.
5657
describe("createLineBufferedLoggers", () => {
5758
it("should create separate buffers for stdout and stderr", () => {
5859
const stdoutLines: string[] = [];
@@ -109,3 +110,35 @@ describe("createLineBufferedLoggers", () => {
109110
expect(stderrLines).toEqual(["also incomplete"]);
110111
});
111112
});
113+
114+
describe("getMuxEnv", () => {
115+
it("should include base MUX_ environment variables", () => {
116+
const env = getMuxEnv("/path/to/project", "worktree", "feature-branch");
117+
118+
expect(env.MUX_PROJECT_PATH).toBe("/path/to/project");
119+
expect(env.MUX_RUNTIME).toBe("worktree");
120+
expect(env.MUX_WORKSPACE_NAME).toBe("feature-branch");
121+
expect(env.MUX_MODEL_STRING).toBeUndefined();
122+
expect(env.MUX_THINKING_LEVEL).toBeUndefined();
123+
});
124+
125+
it("should include model + thinking env vars when provided", () => {
126+
const env = getMuxEnv("/path/to/project", "worktree", "feature-branch", {
127+
modelString: "openai:gpt-5.2-pro",
128+
thinkingLevel: "medium",
129+
});
130+
131+
expect(env.MUX_MODEL_STRING).toBe("openai:gpt-5.2-pro");
132+
expect(env.MUX_THINKING_LEVEL).toBe("medium");
133+
});
134+
135+
it("should allow explicit thinkingLevel=off", () => {
136+
const env = getMuxEnv("/path/to/project", "local", "main", {
137+
modelString: "anthropic:claude-3-5-sonnet",
138+
thinkingLevel: "off",
139+
});
140+
141+
expect(env.MUX_MODEL_STRING).toBe("anthropic:claude-3-5-sonnet");
142+
expect(env.MUX_THINKING_LEVEL).toBe("off");
143+
});
144+
});

src/node/runtime/initHook.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as fsPromises from "fs/promises";
33
import * as path from "path";
44
import type { InitLogger } from "./Runtime";
55
import type { RuntimeConfig } from "@/common/types/runtime";
6+
import type { ThinkingLevel } from "@/common/types/thinking";
67
import { isWorktreeRuntime, isSSHRuntime } from "@/common/types/runtime";
78

89
/**
@@ -38,13 +39,34 @@ export function getInitHookPath(projectPath: string): string {
3839
export function getMuxEnv(
3940
projectPath: string,
4041
runtime: "local" | "worktree" | "ssh",
41-
workspaceName: string
42+
workspaceName: string,
43+
options?: {
44+
modelString?: string;
45+
thinkingLevel?: ThinkingLevel;
46+
}
4247
): Record<string, string> {
43-
return {
48+
if (!projectPath) {
49+
throw new Error("getMuxEnv: projectPath is required");
50+
}
51+
if (!workspaceName) {
52+
throw new Error("getMuxEnv: workspaceName is required");
53+
}
54+
55+
const env: Record<string, string> = {
4456
MUX_PROJECT_PATH: projectPath,
4557
MUX_RUNTIME: runtime,
4658
MUX_WORKSPACE_NAME: workspaceName,
4759
};
60+
61+
if (options?.modelString) {
62+
env.MUX_MODEL_STRING = options.modelString;
63+
}
64+
65+
if (options?.thinkingLevel !== undefined) {
66+
env.MUX_THINKING_LEVEL = options.thinkingLevel;
67+
}
68+
69+
return env;
4870
}
4971

5072
/**

src/node/services/aiService.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1150,7 +1150,11 @@ export class AIService extends EventEmitter {
11501150
muxEnv: getMuxEnv(
11511151
metadata.projectPath,
11521152
getRuntimeType(metadata.runtimeConfig),
1153-
metadata.name
1153+
metadata.name,
1154+
{
1155+
modelString,
1156+
thinkingLevel: thinkingLevel ?? "off",
1157+
}
11541158
),
11551159
runtimeTempDir,
11561160
backgroundProcessManager: this.backgroundProcessManager,

0 commit comments

Comments
 (0)