Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/tower-experimental-flag.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@moonshot-ai/kimi-code": patch
---

Gate Tower multi-agent orchestration behind an experimental flag: it is off by default, opt in with `KIMI_CODE_EXPERIMENTAL_TOWER=1` or by setting `tower = true` in the `[experimental]` config section.
16 changes: 16 additions & 0 deletions packages/agent-core-v2/src/features/tower/flag.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { type FlagDefinitionInput, registerFlagDefinition } from '#/app/flag/flagRegistry';

import { TOWER_FLAG_ID } from './tower';

export const TOWER_FLAG_ENV = 'KIMI_CODE_EXPERIMENTAL_TOWER';

export const towerFlag: FlagDefinitionInput = {
id: TOWER_FLAG_ID,
title: 'Tower multi-agent orchestration',
description: 'Coordinate parallel worker agents through Tower tools and the tower mode.',
env: TOWER_FLAG_ENV,
default: false,
surface: 'core',
};

registerFlagDefinition(towerFlag);
1 change: 1 addition & 0 deletions packages/agent-core-v2/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,7 @@ import '#/agent/goal/goalDeadlineSchedulerService';
export * from '#/agent/goal/goal';
export * from '#/agent/goal/goalService';
export * from '#/agent/goal/types';
export * from '#/features/tower/flag';
export * from '#/features/tower/tower';
export * from '#/features/tower/towerService';
export * from '#/features/tower/towerRateLimit';
Expand Down
114 changes: 114 additions & 0 deletions packages/agent-core-v2/test/app/flag/flagWiring.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
import { dirname, join, relative, resolve } from 'node:path';

import { describe, expect, it } from 'vitest';

const sourceRoot = join(import.meta.dirname, '../../../src');
const packageEntry = join(sourceRoot, 'index.ts');
const flagRegistryModule = join(sourceRoot, 'app/flag/flagRegistry.ts');

const RUNTIME_EDGE =
/(?:^|\n)\s*(?:import\s+(?!type[\s{])[^'"]*?from\s+|import\s+|export\s+(?!type[\s{])[^'"]*?from\s+)['"]([^'"]+)['"]/g;
Comment thread
bj456736 marked this conversation as resolved.
const REGISTRATION_CALL = /\bregisterFlagDefinition\s*\(/;
const FLAG_ID_CONSTANT = /export\s+const\s+([A-Z0-9_]+_FLAG_ID)\s*=\s*['"]([^'"]+)['"]/g;

function keepsRuntimeSideEffects(statement: string): boolean {
const open = statement.indexOf('{');
if (open === -1) return true;
const inner = statement.slice(open + 1, statement.lastIndexOf('}'));
return inner
.split(',')
.map((part) => part.trim())
.filter(Boolean)
.some((part) => !part.startsWith('type '));
}

function listTypeScriptFiles(root: string): string[] {
const result: string[] = [];
const visit = (directory: string): void => {
for (const entry of readdirSync(directory, { withFileTypes: true })) {
const absolute = join(directory, entry.name);
if (entry.isDirectory()) visit(absolute);
else if (entry.name.endsWith('.ts')) result.push(absolute);
}
};
visit(root);
return result;
}

function resolveSpecifier(fromFile: string, specifier: string): string | undefined {
let base: string;
if (specifier.startsWith('#/')) base = join(sourceRoot, specifier.slice(2));
else if (specifier.startsWith('.')) base = resolve(dirname(fromFile), specifier);
else return undefined;
const candidates = specifier.endsWith('.js')
? [base.replace(/\.js$/, '.ts'), base.replace(/\.js$/, '.tsx')]
: [base, `${base}.ts`, `${base}.tsx`, join(base, 'index.ts'), join(base, 'index.tsx')];
for (const candidate of candidates) {
if (existsSync(candidate) && statSync(candidate).isFile()) return candidate;
}
return undefined;
}

function collectModulesReachableFromEntry(): Set<string> {
const reachable = new Set<string>();
const queue = [packageEntry];
while (queue.length > 0) {
const file = queue.pop()!;
if (reachable.has(file)) continue;
reachable.add(file);
const content = readFileSync(file, 'utf8').replaceAll(/\/\*[\s\S]*?\*\//g, ' ');
for (const match of content.matchAll(RUNTIME_EDGE)) {
if (!keepsRuntimeSideEffects(match[0])) continue;
const resolved = resolveSpecifier(file, match[1]!);
if (resolved && !reachable.has(resolved)) queue.push(resolved);
}
}
return reachable;
}

function findRegistrationModules(): string[] {
return listTypeScriptFiles(sourceRoot)
.filter((file) => file !== flagRegistryModule)
.filter((file) => REGISTRATION_CALL.test(readFileSync(file, 'utf8')));
}

describe('experimental flag wiring', () => {
it('loads every module that registers a flag definition from the package entry', () => {
const reachable = collectModulesReachableFromEntry();
const unwired = findRegistrationModules()
.filter((file) => !reachable.has(file))
.map((file) => relative(sourceRoot, file));
expect(
unwired,
'these modules call registerFlagDefinition but are not reachable from src/index.ts, ' +
'so their flags never get registered and every gate on them silently stays off. ' +
"Import each module for its side effects from the package entry (see how src/index.ts imports '#/…/flag').",
).toEqual([]);
});

it('registers every exported *_FLAG_ID constant', () => {
const registrationModules = findRegistrationModules();
const registrationSource = registrationModules
.map((file) => readFileSync(file, 'utf8'))
.join('\n');
const unregistered: string[] = [];
for (const file of listTypeScriptFiles(sourceRoot)) {
if (registrationModules.includes(file)) continue;
const content = readFileSync(file, 'utf8');
for (const match of content.matchAll(FLAG_ID_CONSTANT)) {
const name = match[1]!;
const id = match[2]!;
if (!new RegExp(`\\b${name}\\b`).test(registrationSource)) {
unregistered.push(`${name} ('${id}') in ${relative(sourceRoot, file)}`);
}
}
}
expect(
unregistered,
'these exported flag id constants are never referenced by a module calling registerFlagDefinition, ' +
'so gates evaluating them silently stay off. ' +
'Register each flag in a flag module wired into the package entry (see src/features/tower/flag.ts).',
).toEqual([]);
});
});
59 changes: 47 additions & 12 deletions packages/agent-core-v2/test/features/tower/towerFeature.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
_clearFeatureRecipesForTests,
registerFeature,
} from '#/features/featureRegistry';
import { TOWER_FLAG_ENV } from '#/features/tower/flag';
import { TOWER_FLAG_ID } from '#/features/tower/tower';
import { ITowerRateLimitService } from '#/features/tower/towerRateLimit';
import { TowerFeature } from '#/features/tower/towerFeature';
Expand Down Expand Up @@ -116,7 +117,7 @@ describe('TowerFeature — experimental flag gating', () => {
});
});

describe('tower flag — hard-disabled (no declaration registered)', () => {
describe('tower flag — resolution', () => {
let disposables: DisposableStore;
let homeDir: string;

Expand All @@ -139,19 +140,53 @@ describe('tower flag — hard-disabled (no declaration registered)', () => {
return { config: ix.get(IConfigService), flags: ix.get(IFlagService) };
}

it('cannot be enabled by the dedicated or master env while no tower flag is registered', () => {
const { flags } = makeFlags({
KIMI_CODE_EXPERIMENTAL_TOWER: 'true',
[MASTER_ENV]: 'true',
it('is registered and disabled by default', () => {
const { flags } = makeFlags();
expect(flags.explain(TOWER_FLAG_ID)).toMatchObject({
id: TOWER_FLAG_ID,
env: TOWER_FLAG_ENV,
defaultEnabled: false,
enabled: false,
source: 'default',
});
});

it('is enabled by the dedicated env', () => {
const { flags } = makeFlags({ [TOWER_FLAG_ENV]: '1' });
expect(flags.explain(TOWER_FLAG_ID)).toMatchObject({
enabled: true,
source: 'env',
});
});

it('uses config unless the dedicated env overrides it', async () => {
const configured = makeFlags();
await configured.config.set(EXPERIMENTAL_SECTION, { [TOWER_FLAG_ID]: true });
expect(configured.flags.explain(TOWER_FLAG_ID)).toMatchObject({
enabled: true,
source: 'config',
configValue: true,
});

const overridden = makeFlags({ [TOWER_FLAG_ENV]: 'false' });
await overridden.config.set(EXPERIMENTAL_SECTION, { [TOWER_FLAG_ID]: true });
expect(overridden.flags.explain(TOWER_FLAG_ID)).toMatchObject({
enabled: false,
source: 'env',
configValue: true,
});
expect(flags.explain(TOWER_FLAG_ID)).toBeUndefined();
expect(flags.enabled(TOWER_FLAG_ID)).toBe(false);
});

it('cannot be enabled through the [experimental] config section', async () => {
const { config, flags } = makeFlags();
await config.set(EXPERIMENTAL_SECTION, { [TOWER_FLAG_ID]: true });
expect(flags.explain(TOWER_FLAG_ID)).toBeUndefined();
expect(flags.enabled(TOWER_FLAG_ID)).toBe(false);
it('lets the master env override the dedicated env and config', async () => {
const { config, flags } = makeFlags({
[TOWER_FLAG_ENV]: 'false',
[MASTER_ENV]: 'true',
});
await config.set(EXPERIMENTAL_SECTION, { [TOWER_FLAG_ID]: false });
expect(flags.explain(TOWER_FLAG_ID)).toMatchObject({
enabled: true,
source: 'master-env',
configValue: false,
});
});
});
Loading