Compare commits

...

3 Commits

Author SHA1 Message Date
Aishanee Shah 45df40c513 feat: enable thin harness by default on this branch 2026-03-17 14:34:54 +00:00
Aishanee Shah 7bcb1280ad feat: restrict tools to core only in thin harness mode 2026-03-17 14:33:27 +00:00
Aishanee Shah a39866a7cd feat: implement thin harness mode 2026-03-17 14:07:44 +00:00
4 changed files with 103 additions and 27 deletions
+9
View File
@@ -104,6 +104,11 @@ export class AgentRegistry {
private async loadAgents(): Promise<void> { private async loadAgents(): Promise<void> {
this.agents.clear(); this.agents.clear();
this.allDefinitions.clear(); this.allDefinitions.clear();
if (this.config.isThinHarness()) {
return;
}
this.loadBuiltInAgents(); this.loadBuiltInAgents();
if (!this.config.isAgentsEnabled()) { if (!this.config.isAgentsEnabled()) {
@@ -240,6 +245,10 @@ export class AgentRegistry {
} }
private loadBuiltInAgents(): void { private loadBuiltInAgents(): void {
if (this.config.isThinHarness()) {
return;
}
this.registerLocalAgent(CodebaseInvestigatorAgent(this.config)); this.registerLocalAgent(CodebaseInvestigatorAgent(this.config));
this.registerLocalAgent(CliHelpAgent(this.config)); this.registerLocalAgent(CliHelpAgent(this.config));
this.registerLocalAgent(GeneralistAgent(this.config)); this.registerLocalAgent(GeneralistAgent(this.config));
+70 -22
View File
@@ -633,6 +633,7 @@ export interface ConfigParameters {
tracker?: boolean; tracker?: boolean;
planSettings?: PlanSettings; planSettings?: PlanSettings;
modelSteering?: boolean; modelSteering?: boolean;
thinHarness?: boolean;
onModelChange?: (model: string) => void; onModelChange?: (model: string) => void;
mcpEnabled?: boolean; mcpEnabled?: boolean;
extensionsEnabled?: boolean; extensionsEnabled?: boolean;
@@ -853,6 +854,7 @@ export class Config implements McpContext, AgentLoopContext {
private readonly planEnabled: boolean; private readonly planEnabled: boolean;
private readonly trackerEnabled: boolean; private readonly trackerEnabled: boolean;
private readonly planModeRoutingEnabled: boolean; private readonly planModeRoutingEnabled: boolean;
private readonly thinHarness: boolean;
private readonly modelSteering: boolean; private readonly modelSteering: boolean;
private contextManager?: ContextManager; private contextManager?: ContextManager;
private terminalBackground: string | undefined = undefined; private terminalBackground: string | undefined = undefined;
@@ -996,6 +998,8 @@ export class Config implements McpContext, AgentLoopContext {
modelConfigServiceConfig ?? DEFAULT_MODEL_CONFIGS, modelConfigServiceConfig ?? DEFAULT_MODEL_CONFIGS,
); );
this.thinHarness =
params.thinHarness ?? process.env['GEMINI_THIN_HARNESS'] !== 'false';
this.experimentalJitContext = params.experimentalJitContext ?? true; this.experimentalJitContext = params.experimentalJitContext ?? true;
this.topicUpdateNarration = params.topicUpdateNarration ?? false; this.topicUpdateNarration = params.topicUpdateNarration ?? false;
this.modelSteering = params.modelSteering ?? false; this.modelSteering = params.modelSteering ?? false;
@@ -1236,30 +1240,35 @@ export class Config implements McpContext, AgentLoopContext {
this._toolRegistry = await this.createToolRegistry(); this._toolRegistry = await this.createToolRegistry();
discoverToolsHandle?.end(); discoverToolsHandle?.end();
this.mcpClientManager = new McpClientManager(
this.clientVersion, if (this.thinHarness) {
this._toolRegistry, this.mcpInitializationPromise = Promise.resolve();
this, } else {
this.eventEmitter, this.mcpClientManager = new McpClientManager(
); this.clientVersion,
// We do not await this promise so that the CLI can start up even if this._toolRegistry,
// MCP servers are slow to connect. this,
this.mcpInitializationPromise = Promise.allSettled([ this.eventEmitter,
this.mcpClientManager.startConfiguredMcpServers(), );
this.getExtensionLoader().start(this), // We do not await this promise so that the CLI can start up even if
]).then((results) => { // MCP servers are slow to connect.
for (const result of results) { this.mcpInitializationPromise = Promise.allSettled([
if (result.status === 'rejected') { this.mcpClientManager.startConfiguredMcpServers(),
debugLogger.error('Error initializing MCP clients:', result.reason); this.getExtensionLoader().start(this),
]).then((results) => {
for (const result of results) {
if (result.status === 'rejected') {
debugLogger.error('Error initializing MCP clients:', result.reason);
}
} }
} });
}); }
if (!this.interactive || this.acpMode) { if (!this.interactive || this.acpMode) {
await this.mcpInitializationPromise; await this.mcpInitializationPromise;
} }
if (this.skillsSupport) { if (this.skillsSupport && !this.thinHarness) {
this.getSkillManager().setAdminSettings(this.adminSkillsEnabled); this.getSkillManager().setAdminSettings(this.adminSkillsEnabled);
if (this.adminSkillsEnabled) { if (this.adminSkillsEnabled) {
await this.getSkillManager().discoverSkills( await this.getSkillManager().discoverSkills(
@@ -2024,6 +2033,10 @@ export class Config implements McpContext, AgentLoopContext {
} }
getUserMemory(): string | HierarchicalMemory { getUserMemory(): string | HierarchicalMemory {
if (this.thinHarness) {
return { global: '', extension: '', project: '' };
}
if (this.experimentalJitContext && this.contextManager) { if (this.experimentalJitContext && this.contextManager) {
return { return {
global: this.contextManager.getGlobalMemory(), global: this.contextManager.getGlobalMemory(),
@@ -2120,6 +2133,9 @@ export class Config implements McpContext, AgentLoopContext {
} }
getGeminiMdFileCount(): number { getGeminiMdFileCount(): number {
if (this.thinHarness) {
return 0;
}
if (this.experimentalJitContext && this.contextManager) { if (this.experimentalJitContext && this.contextManager) {
return this.contextManager.getLoadedPaths().size; return this.contextManager.getLoadedPaths().size;
} }
@@ -2131,6 +2147,9 @@ export class Config implements McpContext, AgentLoopContext {
} }
getGeminiMdFilePaths(): string[] { getGeminiMdFilePaths(): string[] {
if (this.thinHarness) {
return [];
}
if (this.experimentalJitContext && this.contextManager) { if (this.experimentalJitContext && this.contextManager) {
return Array.from(this.contextManager.getLoadedPaths()); return Array.from(this.contextManager.getLoadedPaths());
} }
@@ -2493,7 +2512,7 @@ export class Config implements McpContext, AgentLoopContext {
} }
isAgentsEnabled(): boolean { isAgentsEnabled(): boolean {
return this.enableAgents; return this.enableAgents && !this.thinHarness;
} }
isEventDrivenSchedulerEnabled(): boolean { isEventDrivenSchedulerEnabled(): boolean {
@@ -2786,14 +2805,14 @@ export class Config implements McpContext, AgentLoopContext {
} }
isSkillsSupportEnabled(): boolean { isSkillsSupportEnabled(): boolean {
return this.skillsSupport; return this.skillsSupport && !this.thinHarness;
} }
/** /**
* Reloads skills by re-discovering them from extensions and local directories. * Reloads skills by re-discovering them from extensions and local directories.
*/ */
async reloadSkills(): Promise<void> { async reloadSkills(): Promise<void> {
if (!this.skillsSupport) { if (!this.skillsSupport || this.thinHarness) {
return; return;
} }
@@ -2843,6 +2862,10 @@ export class Config implements McpContext, AgentLoopContext {
} }
} }
isThinHarness(): boolean {
return this.thinHarness;
}
isInteractive(): boolean { isInteractive(): boolean {
return this.interactive; return this.interactive;
} }
@@ -3041,6 +3064,25 @@ export class Config implements McpContext, AgentLoopContext {
); );
} }
if (this.thinHarness) {
const allowedTools = [
'LSTool',
'ReadFileTool',
'RipGrepTool',
'GrepTool',
'GlobTool',
'EditTool',
'WriteFileTool',
'WebFetchTool',
'ShellTool',
'WebSearchTool',
'AskUserTool',
];
if (!allowedTools.includes(normalizedClassName)) {
isEnabled = false;
}
}
if (isEnabled) { if (isEnabled) {
registerFn(); registerFn();
} }
@@ -3144,7 +3186,9 @@ export class Config implements McpContext, AgentLoopContext {
// Register Subagents as Tools // Register Subagents as Tools
this.registerSubAgentTools(registry); this.registerSubAgentTools(registry);
await registry.discoverAllTools(); if (!this.thinHarness) {
await registry.discoverAllTools();
}
registry.sortTools(); registry.sortTools();
return registry; return registry;
} }
@@ -3153,6 +3197,10 @@ export class Config implements McpContext, AgentLoopContext {
* Registers SubAgentTools for all available agents. * Registers SubAgentTools for all available agents.
*/ */
private registerSubAgentTools(registry: ToolRegistry): void { private registerSubAgentTools(registry: ToolRegistry): void {
if (this.thinHarness) {
return;
}
const agentsOverrides = this.getAgentsSettings().overrides ?? {}; const agentsOverrides = this.getAgentsSettings().overrides ?? {};
const definitions = this.agentRegistry.getAllDefinitions(); const definitions = this.agentRegistry.getAllDefinitions();
+12 -5
View File
@@ -44,6 +44,11 @@ export class PromptProvider {
userMemory?: string | HierarchicalMemory, userMemory?: string | HierarchicalMemory,
interactiveOverride?: boolean, interactiveOverride?: boolean,
): string { ): string {
const config = context.config;
if (config.isThinHarness()) {
return 'You are Gemini CLI, a helpful assistant specializing in software engineering tasks.';
}
const systemMdResolution = resolvePathFromEnv( const systemMdResolution = resolvePathFromEnv(
process.env['GEMINI_SYSTEM_MD'], process.env['GEMINI_SYSTEM_MD'],
); );
@@ -216,11 +221,13 @@ export class PromptProvider {
} }
// --- Finalization (Shell) --- // --- Finalization (Shell) ---
const finalPrompt = activeSnippets.renderFinalShell( const finalPrompt = context.config.isThinHarness()
basePrompt, ? basePrompt
userMemory, : activeSnippets.renderFinalShell(
contextFilenames, basePrompt,
); userMemory,
contextFilenames,
);
// Sanitize erratic newlines from composition // Sanitize erratic newlines from composition
const sanitizedPrompt = finalPrompt.replace(/\n{3,}/g, '\n\n'); const sanitizedPrompt = finalPrompt.replace(/\n{3,}/g, '\n\n');
@@ -699,6 +699,18 @@ export async function loadServerHierarchicalMemory(
* Returns the result of the call to `loadHierarchicalGeminiMemory`. * Returns the result of the call to `loadHierarchicalGeminiMemory`.
*/ */
export async function refreshServerHierarchicalMemory(config: Config) { export async function refreshServerHierarchicalMemory(config: Config) {
if (config.isThinHarness()) {
const emptyResult: LoadServerHierarchicalMemoryResponse = {
memoryContent: { global: '', extension: '', project: '' },
fileCount: 0,
filePaths: [],
};
config.setUserMemory(emptyResult.memoryContent);
config.setGeminiMdFileCount(0);
config.setGeminiMdFilePaths([]);
return emptyResult;
}
const result = await loadServerHierarchicalMemory( const result = await loadServerHierarchicalMemory(
config.getWorkingDir(), config.getWorkingDir(),
config.shouldLoadMemoryFromIncludeDirectories() config.shouldLoadMemoryFromIncludeDirectories()