Skip to content
This repository was archived by the owner on Feb 25, 2026. It is now read-only.
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ target
opencode-dev
logs/
*.bun-build
.local/

# Telemetry ID
telemetry-id
telemetry-id
97 changes: 97 additions & 0 deletions docs/warm-agents-architect-prompt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# Warm Agents Orchestration — Architect Prompt (Internal)

Use this prompt when you want an architecture-focused design pass for introducing **Warm Agents** into Kilo.

## Prompt

You are the **Architect Agent** for Kilo. Your mission is to design a deterministic, stateful orchestration system called **Warm Agents**.

### Core Intent
Design an architecture where agent execution behaves like a high-reliability dispatch system:
- **Validate intent** before action
- **Control scope** before mutation
- **Persist state** for recovery, handoff, and CI/CD replay

### Mental Model: Mining Fleet Operations (Morenci-style parallels)
Ground your design in operational control concepts used in large haul-truck fleets:
- **MIS-style status tracking**: every agent and task has explicit lifecycle state and telemetry
- **JIT dispatching**: assign work to the warmest qualified agent at the right time, not first-available
- **TPS discipline**: preserve flow, reduce queue buildup, and minimize rework loops
- **Safety interlocks**: no movement without preconditions; no silent failure; deterministic stop modes

Translate this into software architecture with strict contracts, not narrative guidance.

### Required Outcomes
Produce an architecture proposal with:
1. **Subsystem map** for Warm Agents (scheduler, state store, capability registry, invariant middleware, replay/rollback)
2. **Typed lifecycle model** for agent/task/session states
3. **Deterministic routing rules** for selecting/rehydrating warm agents
4. **State durability design** across process restarts and `--auto` unattended runs
5. **Safety model** for blast-radius control, postcondition checks, and rollback behavior
6. **MCP-aware capability routing** that adapts to live tool availability changes
7. **Migration plan** from current Kilo orchestration to Warm Agents with low merge-conflict footprint

### Hard Constraints
- Assume Kilo’s current architecture has:
- durable session/message storage,
- mixed in-memory runtime state,
- prompt-led orchestration behavior,
- tool schema validation but limited cross-tool invariants,
- `--auto` with permission auto-approval.
- Keep proposals compatible with current code organization in `packages/opencode/src/`.
- Prefer additive seams over invasive rewrites.
- Separate **prototype scope** from **production-hardening scope**.

### Deliverable Format
Return exactly these sections:

1. **Executive Summary** (max 10 bullet points)
2. **Architecture Blueprint**
- Components
- Data flows
- Failure domains
3. **State & Contract Schema**
- Agent state machine
- Task state machine
- Session continuity schema
4. **Deterministic Orchestration Policy**
- Rule evaluation order
- Override/deny semantics
- Audit log model
5. **Warmness Model**
- How warm context is scored
- Expiration/staleness rules
- Rehydration strategy
6. **Safety Harness Design for `--auto`**
- Snapshot strategy
- Blast radius declaration
- Rollback protocol
- Structured failure report schema
7. **MCP Lifecycle Awareness Plan**
- Health checks
- Tool schema drift handling
- Runtime routing fallback
8. **Implementation Plan**
- 3 phases (prototype, integration, hardening)
- Files likely touched
- Risks and mitigations
9. **60-Second Demo Script**
- Concrete command flow
- Expected observable behavior
10. **Acceptance Criteria**
- Determinism checks
- Recovery checks
- Safety checks

### Quality Bar
Your design is acceptable only if a senior developer can answer all three:
- What was the agent trying to do?
- What was it allowed to change?
- What state survives process death?

If any answer is unclear, revise the design until explicit.

---

## Optional Usage Note
Use this prompt as internal architecture guidance while exploring contributions around deterministic orchestration and warm-context reuse.
1,076 changes: 1,076 additions & 0 deletions docs/warm-agents-architecture.md

Large diffs are not rendered by default.

83 changes: 83 additions & 0 deletions packages/opencode/src/cli/cmd/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,13 @@ export const RunCommand = cmd({
default: false,
})
// kilocode_change end
// kilocode_change start - warm agents orchestration
.option("warm", {
type: "boolean",
describe: "enable warm agents orchestration with warmness scoring and blast-radius enforcement",
default: false,
})
// kilocode_change end
)
},
handler: async (args) => {
Expand Down Expand Up @@ -449,6 +456,19 @@ export const RunCommand = cmd({
if (part.type === "tool" && part.state.status === "completed") {
if (emit("tool_use", { part })) continue
tool(part)
// kilocode_change start - warm agent status after tool completion
if (args.warm && args.format !== "json") {
const warmCtx = (globalThis as any).__warmContext
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Direct (globalThis as any).__warmContext access bypasses the centralized WarmIntegration.getContext() that was created specifically to encapsulate this.

Since WarmIntegration is already imported on line 543, consider using WarmIntegration.getContext() here instead for consistency and type safety. The same applies to line 540.

if (warmCtx?.enabled && warmCtx.activeTask) {
const output = part.state.output ?? ""
if (output.startsWith("[warm]") && output.includes("blocked")) {
UI.println(UI.Style.TEXT_DANGER_BOLD + " " + output)
} else {
UI.println(UI.Style.TEXT_DIM + " [warm] \u2713 " + part.tool + " within blast radius")
}
}
}
// kilocode_change end
}

if (
Expand Down Expand Up @@ -515,6 +535,29 @@ export const RunCommand = cmd({
event.properties.sessionID === sessionID &&
event.properties.status.type === "idle"
) {
// kilocode_change start - warm agent task completion
if (args.warm) {
const warmCtx = (globalThis as any).__warmContext
if (warmCtx?.enabled && warmCtx.activeTask) {
const { WarmSession } = await import("../../warm/warm-session")
const { WarmIntegration } = await import("../../warm/integration")
const result = await WarmSession.completeTask(warmCtx).catch(() => ({ passed: true, failures: [] }))
if (args.format !== "json") {
UI.empty()
if (result.passed) {
UI.println(UI.Style.TEXT_SUCCESS_BOLD + "~", UI.Style.TEXT_NORMAL + "[warm] Task completed successfully")
} else {
UI.println(UI.Style.TEXT_DANGER_BOLD + "~", UI.Style.TEXT_NORMAL + "[warm] Task completed with failures:")
for (const f of result.failures) {
UI.println(UI.Style.TEXT_DIM + " - " + f)
}
}
const status = WarmIntegration.formatStatus()
if (status) UI.println(UI.Style.TEXT_DIM + " " + status)
}
}
}
// kilocode_change end
break
}

Expand Down Expand Up @@ -576,6 +619,46 @@ export const RunCommand = cmd({
}
await share(sdk, sessionID)

// kilocode_change start - warm agents orchestration
if (args.warm) {
const { WarmSession } = await import("../../warm/warm-session")
const { WarmIntegration } = await import("../../warm/integration")
const warmCtx = WarmSession.createContext(sessionID, {
autoApproveDispatch: args.auto ?? false,
})
WarmIntegration.setContext(warmCtx)

// Register primary agent
await WarmSession.registerAgent(warmCtx, {
id: `agent_${sessionID.slice(0, 16)}`,
agentName: agent ?? "code",
capabilities: ["read", "edit", "bash", "write", "glob", "grep", "webfetch", "websearch", "task"],
})

// Create default task from message
await WarmSession.createDefaultTask(warmCtx, {
message: message || "interactive session",
workingDirectory: process.cwd().replace(/\\/g, "/"),
})

UI.empty()
UI.println(
UI.Style.TEXT_INFO_BOLD + "~",
UI.Style.TEXT_NORMAL + "[warm] Warm Agents orchestration enabled",
)
UI.println(
UI.Style.TEXT_DIM + " agent: " + warmCtx.activeAgent?.id + " (" + warmCtx.activeAgent?.lifecycle + ")",
)
UI.println(
UI.Style.TEXT_DIM + " task: " + warmCtx.activeTask?.id + " (" + warmCtx.activeTask?.lifecycle + ")",
)
UI.println(
UI.Style.TEXT_DIM + " scope: " + warmCtx.activeTask?.blastRadius.paths.join(", "),
)
UI.empty()
}
// kilocode_change end

loop().catch((e) => {
console.error(e)
process.exit(1)
Expand Down
19 changes: 16 additions & 3 deletions packages/opencode/src/cli/cmd/tui/thread.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,14 @@ export const TuiThreadCommand = cmd({
.option("agent", {
type: "string",
describe: "agent to use",
})
// kilocode_change start - warm agents orchestration
.option("warm", {
type: "boolean",
describe: "enable warm agents orchestration with warmness scoring and blast-radius enforcement",
default: false,
}),
// kilocode_change end
handler: async (args) => {
// Keep ENABLE_PROCESSED_INPUT cleared even if other code flips it.
// (Important when running under `bun run` wrappers on Windows.)
Expand Down Expand Up @@ -109,10 +116,16 @@ export const TuiThreadCommand = cmd({
return
}

// kilocode_change start - pass warm flag to worker via environment
const workerEnv = Object.fromEntries(
Object.entries(process.env).filter((entry): entry is [string, string] => entry[1] !== undefined),
)
if (args.warm) {
workerEnv.KILO_WARM = "1"
}
// kilocode_change end
const worker = new Worker(workerPath, {
env: Object.fromEntries(
Object.entries(process.env).filter((entry): entry is [string, string] => entry[1] !== undefined),
),
env: workerEnv,
})
worker.onerror = (e) => {
Log.Default.error(e)
Expand Down
32 changes: 32 additions & 0 deletions packages/opencode/src/session/prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -810,6 +810,19 @@ export namespace SessionPrompt {
inputSchema: jsonSchema(schema as any),
async execute(args, options) {
const ctx = context(args, options)
// kilocode_change start - warm agent blast-radius enforcement
const warmCheck = await (async () => {
if (!(globalThis as any).__warmContext?.enabled && process.env.KILO_WARM !== "1") return undefined
const { WarmIntegration } = await import("../warm/integration")
return WarmIntegration.checkTool(item.id, args, ctx.sessionID)
})()
if (warmCheck && !warmCheck.allowed) {
return {
output: `[warm] Tool "${item.id}" blocked by blast-radius enforcement: ${warmCheck.reason}`,
title: `[warm] blocked`,
}
}
// kilocode_change end
await Plugin.trigger(
"tool.execute.before",
{
Expand All @@ -832,6 +845,12 @@ export namespace SessionPrompt {
},
result,
)
// kilocode_change start - warm agent audit logging
if (warmCheck?.logged) {
const { WarmIntegration } = await import("../warm/integration")
await WarmIntegration.logToolExecution(ctx.sessionID, item.id, args, 0).catch(() => {})
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[WARNING]: Empty .catch(() => {}) silently swallows audit logging errors, and durationMs is hardcoded to 0

Two issues:

  1. The empty catch violates the project's "no empty catch blocks" rule (AGENTS.md). At minimum, log the error.
  2. durationMs: 0 is always passed — if duration tracking isn't needed, remove the parameter from logToolExecution. If it is needed, measure the actual tool execution time.
Suggested change
await WarmIntegration.logToolExecution(ctx.sessionID, item.id, args, 0).catch(() => {})
await WarmIntegration.logToolExecution(ctx.sessionID, item.id, args, 0).catch((e) => log.warn("warm audit failed", { error: e }))

}
// kilocode_change end
return result
},
})
Expand All @@ -847,6 +866,19 @@ export namespace SessionPrompt {
item.execute = async (args, opts) => {
const ctx = context(args, opts)

// kilocode_change start - warm agent blast-radius enforcement for MCP tools
const warmCheck = await (async () => {
if (!(globalThis as any).__warmContext?.enabled && process.env.KILO_WARM !== "1") return undefined
const { WarmIntegration } = await import("../warm/integration")
return WarmIntegration.checkTool(key, args, ctx.sessionID)
})()
if (warmCheck && !warmCheck.allowed) {
return {
content: [{ type: "text" as const, text: `[warm] MCP tool "${key}" blocked by blast-radius enforcement: ${warmCheck.reason}` }],
}
}
// kilocode_change end

await Plugin.trigger(
"tool.execute.before",
{
Expand Down
27 changes: 27 additions & 0 deletions packages/opencode/src/tool/task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,17 @@ export const TaskTool = Tool.define("task", async (ctx) => {
using _ = defer(() => ctx.abort.removeEventListener("abort", cancel))
const promptParts = await SessionPrompt.resolvePromptParts(params.prompt)

// kilocode_change start - warm agents: create scoped sub-task for sub-agent
let warmSubTask: { taskID: string; parentTaskID: string; narrowed: boolean; scope: string[]; previousTask?: any } | undefined
if ((globalThis as any).__warmContext?.enabled || process.env.KILO_WARM === "1") {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: This check duplicates the logic in WarmIntegration.isEnabled(). Consider using the centralized helper instead:

const { WarmIntegration } = await import("../warm/integration")
if (WarmIntegration.isEnabled()) {

This keeps the warm-context detection logic in one place and avoids direct globalThis access in upstream files.

const { WarmIntegration } = await import("../warm/integration")
warmSubTask = await WarmIntegration.createSubTask(
ctx.sessionID,
params.prompt,
)
}
// kilocode_change end

const result = await SessionPrompt.prompt({
messageID,
sessionID: session.id,
Expand All @@ -142,10 +153,26 @@ export const TaskTool = Tool.define("task", async (ctx) => {
parts: promptParts,
})

// kilocode_change start - warm agents: restore parent task after sub-agent completes
if (warmSubTask?.previousTask) {
const { WarmIntegration } = await import("../warm/integration")
await WarmIntegration.completeSubTask(ctx.sessionID, warmSubTask.previousTask)
}
// kilocode_change end

const text = result.parts.findLast((x) => x.type === "text")?.text ?? ""

// kilocode_change start - warm agents: include scope info in output
const scopeInfo = warmSubTask?.narrowed
? `\n[warm] Sub-task scope: ${warmSubTask.scope.join(", ")} (narrowed from parent)`
: warmSubTask
? `\n[warm] Sub-task scope: ${warmSubTask.scope.join(", ")} (inherited from parent)`
: ""
// kilocode_change end

const output = [
`task_id: ${session.id} (for resuming to continue this task if needed)`,
scopeInfo,
"",
"<task_result>",
text,
Expand Down
Loading