fix(orchestrator): make provider-aware Claude key startup requirements
This commit is contained in:
@@ -406,8 +406,7 @@ AI_PROVIDER=ollama
|
|||||||
OLLAMA_MODEL=llama3.1:latest
|
OLLAMA_MODEL=llama3.1:latest
|
||||||
|
|
||||||
# Claude API Key
|
# Claude API Key
|
||||||
# Required by the orchestrator service in swarm deployment.
|
# Required only when AI_PROVIDER=claude.
|
||||||
# Also used when AI_PROVIDER=claude for other services.
|
|
||||||
# Get your API key from: https://console.anthropic.com/
|
# Get your API key from: https://console.anthropic.com/
|
||||||
CLAUDE_API_KEY=REPLACE_WITH_CLAUDE_API_KEY
|
CLAUDE_API_KEY=REPLACE_WITH_CLAUDE_API_KEY
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
# Orchestrator Configuration
|
# Orchestrator Configuration
|
||||||
ORCHESTRATOR_PORT=3001
|
ORCHESTRATOR_PORT=3001
|
||||||
NODE_ENV=development
|
NODE_ENV=development
|
||||||
|
# AI provider for orchestrator agents: ollama, claude, openai
|
||||||
|
AI_PROVIDER=ollama
|
||||||
|
|
||||||
# Valkey
|
# Valkey
|
||||||
VALKEY_HOST=localhost
|
VALKEY_HOST=localhost
|
||||||
@@ -8,6 +10,7 @@ VALKEY_PORT=6379
|
|||||||
VALKEY_URL=redis://localhost:6379
|
VALKEY_URL=redis://localhost:6379
|
||||||
|
|
||||||
# Claude API
|
# Claude API
|
||||||
|
# Required only when AI_PROVIDER=claude.
|
||||||
CLAUDE_API_KEY=your-api-key-here
|
CLAUDE_API_KEY=your-api-key-here
|
||||||
|
|
||||||
# Docker
|
# Docker
|
||||||
|
|||||||
@@ -186,17 +186,18 @@ pnpm --filter @mosaic/orchestrator lint
|
|||||||
|
|
||||||
Environment variables loaded via `@nestjs/config`. Key variables:
|
Environment variables loaded via `@nestjs/config`. Key variables:
|
||||||
|
|
||||||
| Variable | Description |
|
| Variable | Description |
|
||||||
| -------------------------------- | -------------------------------------------------- |
|
| -------------------------------- | ------------------------------------------------------------ |
|
||||||
| `ORCHESTRATOR_PORT` | HTTP port (default: 3001) |
|
| `ORCHESTRATOR_PORT` | HTTP port (default: 3001) |
|
||||||
| `CLAUDE_API_KEY` | Claude API key for agents |
|
| `AI_PROVIDER` | LLM provider for orchestrator (`ollama`, `claude`, `openai`) |
|
||||||
| `VALKEY_HOST` | Valkey/Redis host (default: localhost) |
|
| `CLAUDE_API_KEY` | Required only when `AI_PROVIDER=claude` |
|
||||||
| `VALKEY_PORT` | Valkey/Redis port (default: 6379) |
|
| `VALKEY_HOST` | Valkey/Redis host (default: localhost) |
|
||||||
| `COORDINATOR_URL` | Quality Coordinator base URL |
|
| `VALKEY_PORT` | Valkey/Redis port (default: 6379) |
|
||||||
| `SANDBOX_ENABLED` | Enable Docker sandbox (true/false) |
|
| `COORDINATOR_URL` | Quality Coordinator base URL |
|
||||||
| `MAX_CONCURRENT_AGENTS` | Maximum concurrent in-memory sessions (default: 2) |
|
| `SANDBOX_ENABLED` | Enable Docker sandbox (true/false) |
|
||||||
| `ORCHESTRATOR_QUEUE_CONCURRENCY` | BullMQ worker concurrency (default: 1) |
|
| `MAX_CONCURRENT_AGENTS` | Maximum concurrent in-memory sessions (default: 2) |
|
||||||
| `SANDBOX_DEFAULT_MEMORY_MB` | Sandbox memory limit in MB (default: 256) |
|
| `ORCHESTRATOR_QUEUE_CONCURRENCY` | BullMQ worker concurrency (default: 1) |
|
||||||
|
| `SANDBOX_DEFAULT_MEMORY_MB` | Sandbox memory limit in MB (default: 256) |
|
||||||
|
|
||||||
## Related Documentation
|
## Related Documentation
|
||||||
|
|
||||||
|
|||||||
@@ -192,7 +192,8 @@ LABEL com.mosaic.security.non-root=true
|
|||||||
|
|
||||||
Sensitive configuration is passed via environment variables:
|
Sensitive configuration is passed via environment variables:
|
||||||
|
|
||||||
- `CLAUDE_API_KEY`: Claude API credentials
|
- `AI_PROVIDER`: Orchestrator LLM provider
|
||||||
|
- `CLAUDE_API_KEY`: Claude credentials (required only for `AI_PROVIDER=claude`)
|
||||||
- `VALKEY_URL`: Cache connection string
|
- `VALKEY_URL`: Cache connection string
|
||||||
|
|
||||||
**Best Practices:**
|
**Best Practices:**
|
||||||
|
|||||||
@@ -181,4 +181,30 @@ describe("orchestratorConfig", () => {
|
|||||||
expect(config.spawner.maxConcurrentAgents).toBe(10);
|
expect(config.spawner.maxConcurrentAgents).toBe(10);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("AI provider config", () => {
|
||||||
|
it("should default aiProvider to ollama when unset", () => {
|
||||||
|
delete process.env.AI_PROVIDER;
|
||||||
|
|
||||||
|
const config = orchestratorConfig();
|
||||||
|
|
||||||
|
expect(config.aiProvider).toBe("ollama");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should normalize AI provider to lowercase", () => {
|
||||||
|
process.env.AI_PROVIDER = " cLaUdE ";
|
||||||
|
|
||||||
|
const config = orchestratorConfig();
|
||||||
|
|
||||||
|
expect(config.aiProvider).toBe("claude");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fallback unsupported AI provider to ollama", () => {
|
||||||
|
process.env.AI_PROVIDER = "bad-provider";
|
||||||
|
|
||||||
|
const config = orchestratorConfig();
|
||||||
|
|
||||||
|
expect(config.aiProvider).toBe("ollama");
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,17 @@
|
|||||||
import { registerAs } from "@nestjs/config";
|
import { registerAs } from "@nestjs/config";
|
||||||
|
const normalizeAiProvider = (): "ollama" | "claude" | "openai" => {
|
||||||
|
const provider = process.env.AI_PROVIDER?.trim().toLowerCase();
|
||||||
|
|
||||||
|
if (!provider) {
|
||||||
|
return "ollama";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (provider !== "ollama" && provider !== "claude" && provider !== "openai") {
|
||||||
|
return "ollama";
|
||||||
|
}
|
||||||
|
|
||||||
|
return provider;
|
||||||
|
};
|
||||||
|
|
||||||
export const orchestratorConfig = registerAs("orchestrator", () => ({
|
export const orchestratorConfig = registerAs("orchestrator", () => ({
|
||||||
host: process.env.HOST ?? process.env.BIND_ADDRESS ?? "127.0.0.1",
|
host: process.env.HOST ?? process.env.BIND_ADDRESS ?? "127.0.0.1",
|
||||||
@@ -14,6 +27,7 @@ export const orchestratorConfig = registerAs("orchestrator", () => ({
|
|||||||
claude: {
|
claude: {
|
||||||
apiKey: process.env.CLAUDE_API_KEY,
|
apiKey: process.env.CLAUDE_API_KEY,
|
||||||
},
|
},
|
||||||
|
aiProvider: normalizeAiProvider(),
|
||||||
docker: {
|
docker: {
|
||||||
socketPath: process.env.DOCKER_SOCKET ?? "/var/run/docker.sock",
|
socketPath: process.env.DOCKER_SOCKET ?? "/var/run/docker.sock",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ describe("AgentSpawnerService", () => {
|
|||||||
// Create mock ConfigService
|
// Create mock ConfigService
|
||||||
mockConfigService = {
|
mockConfigService = {
|
||||||
get: vi.fn((key: string) => {
|
get: vi.fn((key: string) => {
|
||||||
|
if (key === "orchestrator.aiProvider") {
|
||||||
|
return "ollama";
|
||||||
|
}
|
||||||
if (key === "orchestrator.claude.apiKey") {
|
if (key === "orchestrator.claude.apiKey") {
|
||||||
return "test-api-key";
|
return "test-api-key";
|
||||||
}
|
}
|
||||||
@@ -31,19 +34,80 @@ describe("AgentSpawnerService", () => {
|
|||||||
expect(service).toBeDefined();
|
expect(service).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should initialize with Claude API key from config", () => {
|
it("should initialize with default AI provider when API key is omitted", () => {
|
||||||
|
const noClaudeConfigService = {
|
||||||
|
get: vi.fn((key: string) => {
|
||||||
|
if (key === "orchestrator.aiProvider") {
|
||||||
|
return "ollama";
|
||||||
|
}
|
||||||
|
if (key === "orchestrator.spawner.maxConcurrentAgents") {
|
||||||
|
return 20;
|
||||||
|
}
|
||||||
|
if (key === "orchestrator.spawner.sessionCleanupDelayMs") {
|
||||||
|
return 30000;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}),
|
||||||
|
} as unknown as ConfigService;
|
||||||
|
|
||||||
|
const serviceNoKey = new AgentSpawnerService(noClaudeConfigService);
|
||||||
|
expect(serviceNoKey).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should initialize with Claude provider when key is present", () => {
|
||||||
expect(mockConfigService.get).toHaveBeenCalledWith("orchestrator.claude.apiKey");
|
expect(mockConfigService.get).toHaveBeenCalledWith("orchestrator.claude.apiKey");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should throw error if Claude API key is missing", () => {
|
it("should initialize with CLAUDE provider when API key is present", () => {
|
||||||
|
const claudeConfigService = {
|
||||||
|
get: vi.fn((key: string) => {
|
||||||
|
if (key === "orchestrator.aiProvider") {
|
||||||
|
return "claude";
|
||||||
|
}
|
||||||
|
if (key === "orchestrator.claude.apiKey") {
|
||||||
|
return "test-api-key";
|
||||||
|
}
|
||||||
|
if (key === "orchestrator.spawner.maxConcurrentAgents") {
|
||||||
|
return 20;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}),
|
||||||
|
} as unknown as ConfigService;
|
||||||
|
|
||||||
|
const claudeService = new AgentSpawnerService(claudeConfigService);
|
||||||
|
expect(claudeService).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should throw error if Claude API key is missing when provider is claude", () => {
|
||||||
const badConfigService = {
|
const badConfigService = {
|
||||||
get: vi.fn(() => undefined),
|
get: vi.fn((key: string) => {
|
||||||
|
if (key === "orchestrator.aiProvider") {
|
||||||
|
return "claude";
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}),
|
||||||
} as unknown as ConfigService;
|
} as unknown as ConfigService;
|
||||||
|
|
||||||
expect(() => new AgentSpawnerService(badConfigService)).toThrow(
|
expect(() => new AgentSpawnerService(badConfigService)).toThrow(
|
||||||
"CLAUDE_API_KEY is not configured"
|
"CLAUDE_API_KEY is required when AI_PROVIDER is set to 'claude'"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should still initialize when CLAUDE_API_KEY is missing for non-Claude provider", () => {
|
||||||
|
const nonClaudeConfigService = {
|
||||||
|
get: vi.fn((key: string) => {
|
||||||
|
if (key === "orchestrator.aiProvider") {
|
||||||
|
return "ollama";
|
||||||
|
}
|
||||||
|
if (key === "orchestrator.spawner.maxConcurrentAgents") {
|
||||||
|
return 20;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}),
|
||||||
|
} as unknown as ConfigService;
|
||||||
|
|
||||||
|
expect(() => new AgentSpawnerService(nonClaudeConfigService)).not.toThrow();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("spawnAgent", () => {
|
describe("spawnAgent", () => {
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import {
|
|||||||
* This allows time for status queries before the session is removed
|
* This allows time for status queries before the session is removed
|
||||||
*/
|
*/
|
||||||
const DEFAULT_SESSION_CLEANUP_DELAY_MS = 30000; // 30 seconds
|
const DEFAULT_SESSION_CLEANUP_DELAY_MS = 30000; // 30 seconds
|
||||||
|
const SUPPORTED_AI_PROVIDERS = ["ollama", "claude", "openai"] as const;
|
||||||
|
type SupportedAiProvider = (typeof SUPPORTED_AI_PROVIDERS)[number];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service responsible for spawning Claude agents using Anthropic SDK
|
* Service responsible for spawning Claude agents using Anthropic SDK
|
||||||
@@ -21,22 +23,38 @@ const DEFAULT_SESSION_CLEANUP_DELAY_MS = 30000; // 30 seconds
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class AgentSpawnerService implements OnModuleDestroy {
|
export class AgentSpawnerService implements OnModuleDestroy {
|
||||||
private readonly logger = new Logger(AgentSpawnerService.name);
|
private readonly logger = new Logger(AgentSpawnerService.name);
|
||||||
private readonly anthropic: Anthropic;
|
private readonly anthropic: Anthropic | undefined;
|
||||||
|
private readonly aiProvider: SupportedAiProvider;
|
||||||
private readonly sessions = new Map<string, AgentSession>();
|
private readonly sessions = new Map<string, AgentSession>();
|
||||||
private readonly maxConcurrentAgents: number;
|
private readonly maxConcurrentAgents: number;
|
||||||
private readonly sessionCleanupDelayMs: number;
|
private readonly sessionCleanupDelayMs: number;
|
||||||
private readonly cleanupTimers = new Map<string, NodeJS.Timeout>();
|
private readonly cleanupTimers = new Map<string, NodeJS.Timeout>();
|
||||||
|
|
||||||
constructor(private readonly configService: ConfigService) {
|
constructor(private readonly configService: ConfigService) {
|
||||||
|
const configuredProvider = this.configService.get<string>("orchestrator.aiProvider");
|
||||||
|
this.aiProvider = this.normalizeAiProvider(configuredProvider);
|
||||||
|
|
||||||
|
this.logger.log(`AgentSpawnerService resolved AI provider: ${this.aiProvider}`);
|
||||||
|
|
||||||
const apiKey = this.configService.get<string>("orchestrator.claude.apiKey");
|
const apiKey = this.configService.get<string>("orchestrator.claude.apiKey");
|
||||||
|
|
||||||
if (!apiKey) {
|
if (this.aiProvider === "claude") {
|
||||||
throw new Error("CLAUDE_API_KEY is not configured");
|
if (!apiKey) {
|
||||||
}
|
throw new Error("CLAUDE_API_KEY is required when AI_PROVIDER is set to 'claude'");
|
||||||
|
}
|
||||||
|
|
||||||
this.anthropic = new Anthropic({
|
this.logger.log("CLAUDE_API_KEY is configured. Initializing Anthropic client.");
|
||||||
apiKey,
|
this.anthropic = new Anthropic({ apiKey });
|
||||||
});
|
} else {
|
||||||
|
if (apiKey) {
|
||||||
|
this.logger.debug(
|
||||||
|
`CLAUDE_API_KEY is set but ignored because AI provider is '${this.aiProvider}'`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.logger.log(`CLAUDE_API_KEY not required for AI provider '${this.aiProvider}'.`);
|
||||||
|
}
|
||||||
|
this.anthropic = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
// Default to 20 if not configured
|
// Default to 20 if not configured
|
||||||
this.maxConcurrentAgents =
|
this.maxConcurrentAgents =
|
||||||
@@ -48,10 +66,27 @@ export class AgentSpawnerService implements OnModuleDestroy {
|
|||||||
DEFAULT_SESSION_CLEANUP_DELAY_MS;
|
DEFAULT_SESSION_CLEANUP_DELAY_MS;
|
||||||
|
|
||||||
this.logger.log(
|
this.logger.log(
|
||||||
`AgentSpawnerService initialized with Claude SDK (max concurrent agents: ${String(this.maxConcurrentAgents)}, cleanup delay: ${String(this.sessionCleanupDelayMs)}ms)`
|
`AgentSpawnerService initialized with ${this.aiProvider} AI provider (max concurrent agents: ${String(
|
||||||
|
this.maxConcurrentAgents
|
||||||
|
)}, cleanup delay: ${String(this.sessionCleanupDelayMs)}ms)`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private normalizeAiProvider(provider?: string): SupportedAiProvider {
|
||||||
|
const normalizedProvider = provider?.trim().toLowerCase();
|
||||||
|
|
||||||
|
if (!normalizedProvider) {
|
||||||
|
return "ollama";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SUPPORTED_AI_PROVIDERS.includes(normalizedProvider as SupportedAiProvider)) {
|
||||||
|
this.logger.warn(`Unsupported AI provider '${normalizedProvider}'. Defaulting to 'ollama'.`);
|
||||||
|
return "ollama";
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalizedProvider as SupportedAiProvider;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clean up all pending cleanup timers on module destroy
|
* Clean up all pending cleanup timers on module destroy
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -248,7 +248,9 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
NODE_ENV: production
|
NODE_ENV: production
|
||||||
ORCHESTRATOR_PORT: 3001
|
ORCHESTRATOR_PORT: 3001
|
||||||
|
AI_PROVIDER: ${AI_PROVIDER:-ollama}
|
||||||
VALKEY_URL: redis://valkey:6379
|
VALKEY_URL: redis://valkey:6379
|
||||||
|
# Claude API (required only when AI_PROVIDER=claude)
|
||||||
CLAUDE_API_KEY: ${CLAUDE_API_KEY}
|
CLAUDE_API_KEY: ${CLAUDE_API_KEY}
|
||||||
DOCKER_SOCKET: /var/run/docker.sock
|
DOCKER_SOCKET: /var/run/docker.sock
|
||||||
GIT_USER_NAME: "Mosaic Orchestrator"
|
GIT_USER_NAME: "Mosaic Orchestrator"
|
||||||
|
|||||||
@@ -432,9 +432,10 @@ services:
|
|||||||
NODE_ENV: production
|
NODE_ENV: production
|
||||||
# Orchestrator Configuration
|
# Orchestrator Configuration
|
||||||
ORCHESTRATOR_PORT: 3001
|
ORCHESTRATOR_PORT: 3001
|
||||||
|
AI_PROVIDER: ${AI_PROVIDER:-ollama}
|
||||||
# Valkey
|
# Valkey
|
||||||
VALKEY_URL: redis://valkey:6379
|
VALKEY_URL: redis://valkey:6379
|
||||||
# Claude API
|
# Claude API (required only when AI_PROVIDER=claude)
|
||||||
CLAUDE_API_KEY: ${CLAUDE_API_KEY}
|
CLAUDE_API_KEY: ${CLAUDE_API_KEY}
|
||||||
# Docker
|
# Docker
|
||||||
DOCKER_SOCKET: /var/run/docker.sock
|
DOCKER_SOCKET: /var/run/docker.sock
|
||||||
|
|||||||
@@ -441,9 +441,10 @@ services:
|
|||||||
NODE_ENV: production
|
NODE_ENV: production
|
||||||
# Orchestrator Configuration
|
# Orchestrator Configuration
|
||||||
ORCHESTRATOR_PORT: 3001
|
ORCHESTRATOR_PORT: 3001
|
||||||
|
AI_PROVIDER: ${AI_PROVIDER:-ollama}
|
||||||
# Valkey
|
# Valkey
|
||||||
VALKEY_URL: redis://valkey:6379
|
VALKEY_URL: redis://valkey:6379
|
||||||
# Claude API
|
# Claude API (required only when AI_PROVIDER=claude)
|
||||||
CLAUDE_API_KEY: ${CLAUDE_API_KEY}
|
CLAUDE_API_KEY: ${CLAUDE_API_KEY}
|
||||||
# Docker
|
# Docker
|
||||||
DOCKER_SOCKET: /var/run/docker.sock
|
DOCKER_SOCKET: /var/run/docker.sock
|
||||||
|
|||||||
@@ -43,7 +43,10 @@ ENCRYPTION_KEY=$(openssl rand -hex 32)
|
|||||||
ORCHESTRATOR_API_KEY=$(openssl rand -base64 32)
|
ORCHESTRATOR_API_KEY=$(openssl rand -base64 32)
|
||||||
COORDINATOR_API_KEY=$(openssl rand -base64 32)
|
COORDINATOR_API_KEY=$(openssl rand -base64 32)
|
||||||
|
|
||||||
# Claude API Key
|
# AI Provider for Orchestrator
|
||||||
|
AI_PROVIDER=ollama
|
||||||
|
|
||||||
|
# Claude API Key (only required when AI_PROVIDER=claude)
|
||||||
CLAUDE_API_KEY=your-claude-api-key
|
CLAUDE_API_KEY=your-claude-api-key
|
||||||
|
|
||||||
# Authentik Bootstrap
|
# Authentik Bootstrap
|
||||||
|
|||||||
@@ -397,9 +397,9 @@
|
|||||||
|
|
||||||
### Tasks
|
### Tasks
|
||||||
|
|
||||||
| id | status | description | issue | repo | branch | depends_on | blocks | agent | started_at | completed_at | estimate | used |
|
| id | status | description | issue | repo | branch | depends_on | blocks | agent | started_at | completed_at | estimate | used |
|
||||||
| ------------ | ----------- | ----------------------------------------------------------------------------------------------------- | ----- | ----------------- | ---------------------------------------- | -------------------------------------- | ------------ | ----- | ---------- | ------------ | -------- | ---- |
|
| ------------ | ------ | ----------------------------------------------------------------------------------------------------- | ----- | ----------------- | ---------------------------------------- | -------------------------------------- | ------------ | ----- | ---------- | ------------------------- | -------- | ---- |
|
||||||
| ORCH-424-001 | not-started | Remove hard startup dependency on `CLAUDE_API_KEY` unless provider explicitly requires it | #424 | orchestrator | fix/orchestrator-runtime-provider-config | | ORCH-424-002 | | | | 12K | |
|
| ORCH-424-001 | done | Remove hard startup dependency on `CLAUDE_API_KEY` unless provider explicitly requires it | #424 | orchestrator | fix/orchestrator-runtime-provider-config | | ORCH-424-002 | | | 2026-02-17T17:15:18-06:00 | 12K | |
|
||||||
| ORCH-424-002 | not-started | Add provider/runtime-aware validation and startup diagnostics for required key availability | #424 | orchestrator | fix/orchestrator-runtime-provider-config | ORCH-424-001 | ORCH-424-003 | | | | 10K | |
|
| ORCH-424-002 | done | Add provider/runtime-aware validation and startup diagnostics for required key availability | #424 | orchestrator | fix/orchestrator-runtime-provider-config | ORCH-424-001 | ORCH-424-003 | | | 2026-02-17T17:15:18-06:00 | 10K | |
|
||||||
| ORCH-424-003 | not-started | Update env example docs for Codex/OpenCode/Claude multi-provider startup behavior | #424 | orchestrator,docs | fix/orchestrator-runtime-provider-config | ORCH-424-002 | ORCH-424-V01 | | | | 8K | |
|
| ORCH-424-003 | done | Update env example docs for Codex/OpenCode/Claude multi-provider startup behavior | #424 | orchestrator,docs | fix/orchestrator-runtime-provider-config | ORCH-424-002 | ORCH-424-V01 | | | 2026-02-17T17:15:18-06:00 | 8K | |
|
||||||
| ORCH-424-V01 | not-started | Verification: `pnpm lint && pnpm typecheck && pnpm test` healthy for non-Claude startup/runtime paths | #424 | all | fix/orchestrator-runtime-provider-config | ORCH-424-001,ORCH-424-002,ORCH-424-003 | | | | | 5K | |
|
| ORCH-424-V01 | done | Verification: `pnpm lint && pnpm typecheck && pnpm test` healthy for non-Claude startup/runtime paths | #424 | all | fix/orchestrator-runtime-provider-config | ORCH-424-001,ORCH-424-002,ORCH-424-003 | | | | 2026-02-17T17:15:18-06:00 | 5K | |
|
||||||
|
|||||||
Reference in New Issue
Block a user