diff --git a/src/tools/omo-task/constants.ts b/src/tools/omo-task/constants.ts
new file mode 100644
index 0000000..9245d87
--- /dev/null
+++ b/src/tools/omo-task/constants.ts
@@ -0,0 +1,17 @@
+export const ALLOWED_AGENTS = ["explore", "librarian"] as const
+
+export const TASK_TOOL_DESCRIPTION_TEMPLATE = `Launch a new agent to handle complex, multi-step tasks autonomously.
+
+This is a restricted version of the Task tool that only allows spawning explore and librarian agents.
+
+Available agent types:
+{agents}
+
+When using this tool, you must specify a subagent_type parameter to select which agent type to use.
+
+Usage notes:
+1. Launch multiple agents concurrently whenever possible, to maximize performance
+2. When the agent is done, it will return a single message back to you
+3. Each agent invocation is stateless unless you provide a session_id
+4. Your prompt should contain a highly detailed task description for the agent to perform autonomously
+5. Clearly tell the agent whether you expect it to write code or just to do research`
diff --git a/src/tools/omo-task/index.ts b/src/tools/omo-task/index.ts
new file mode 100644
index 0000000..d39a38a
--- /dev/null
+++ b/src/tools/omo-task/index.ts
@@ -0,0 +1,3 @@
+export * from "./types"
+export * from "./constants"
+export { createOmoTask } from "./tools"
diff --git a/src/tools/omo-task/tools.ts b/src/tools/omo-task/tools.ts
new file mode 100644
index 0000000..d8fa317
--- /dev/null
+++ b/src/tools/omo-task/tools.ts
@@ -0,0 +1,111 @@
+import { tool, type PluginInput } from "@opencode-ai/plugin"
+import { ALLOWED_AGENTS, TASK_TOOL_DESCRIPTION_TEMPLATE } from "./constants"
+import type { OmoTaskArgs } from "./types"
+import { log } from "../../shared/logger"
+
+export function createOmoTask(ctx: PluginInput) {
+ const agentDescriptions = ALLOWED_AGENTS.map((name) => `- ${name}: Specialized agent for ${name} tasks`).join(
+ "\n"
+ )
+ const description = TASK_TOOL_DESCRIPTION_TEMPLATE.replace("{agents}", agentDescriptions)
+
+ return tool({
+ description,
+ args: {
+ description: tool.schema.string().describe("A short (3-5 words) description of the task"),
+ prompt: tool.schema.string().describe("The task for the agent to perform"),
+ subagent_type: tool.schema
+ .enum(ALLOWED_AGENTS)
+ .describe("The type of specialized agent to use for this task (explore or librarian only)"),
+ session_id: tool.schema.string().describe("Existing Task session to continue").optional(),
+ },
+ async execute(args: OmoTaskArgs, toolContext) {
+ log(`[omo_task] Starting with agent: ${args.subagent_type}`)
+
+ if (!ALLOWED_AGENTS.includes(args.subagent_type as any)) {
+ return `Error: Invalid agent type "${args.subagent_type}". Only ${ALLOWED_AGENTS.join(", ")} are allowed.`
+ }
+
+ let sessionID: string
+
+ if (args.session_id) {
+ log(`[omo_task] Using existing session: ${args.session_id}`)
+ const sessionResult = await ctx.client.session.get({
+ path: { id: args.session_id },
+ })
+ if (sessionResult.error) {
+ log(`[omo_task] Session get error:`, sessionResult.error)
+ return `Error: Failed to get existing session: ${sessionResult.error}`
+ }
+ sessionID = args.session_id
+ } else {
+ log(`[omo_task] Creating new session with parent: ${toolContext.sessionID}`)
+ const createResult = await ctx.client.session.create({
+ body: {
+ parentID: toolContext.sessionID,
+ title: `${args.description} (@${args.subagent_type} subagent)`,
+ },
+ })
+
+ if (createResult.error) {
+ log(`[omo_task] Session create error:`, createResult.error)
+ return `Error: Failed to create session: ${createResult.error}`
+ }
+
+ sessionID = createResult.data.id
+ log(`[omo_task] Created session: ${sessionID}`)
+ }
+
+ log(`[omo_task] Sending prompt to session ${sessionID}`)
+ log(`[omo_task] Prompt text:`, args.prompt.substring(0, 100))
+
+ await ctx.client.session.prompt({
+ path: { id: sessionID },
+ body: {
+ agent: args.subagent_type,
+ tools: {
+ task: false,
+ omo_task: false,
+ },
+ parts: [{ type: "text", text: args.prompt }],
+ },
+ })
+
+ log(`[omo_task] Prompt sent, fetching messages...`)
+
+ const messagesResult = await ctx.client.session.messages({
+ path: { id: sessionID },
+ })
+
+ if (messagesResult.error) {
+ log(`[omo_task] Messages error:`, messagesResult.error)
+ return `Error: Failed to get messages: ${messagesResult.error}`
+ }
+
+ const messages = messagesResult.data
+ log(`[omo_task] Got ${messages.length} messages`)
+
+ const lastAssistantMessage = messages
+ .filter((m: any) => m.info.role === "assistant")
+ .sort((a: any, b: any) => (b.info.time?.created || 0) - (a.info.time?.created || 0))[0]
+
+ if (!lastAssistantMessage) {
+ log(`[omo_task] No assistant message found`)
+ log(`[omo_task] All messages:`, JSON.stringify(messages, null, 2))
+ return `Error: No assistant response found\n\n\nsession_id: ${sessionID}\n`
+ }
+
+ log(`[omo_task] Found assistant message with ${lastAssistantMessage.parts.length} parts`)
+
+ const textParts = lastAssistantMessage.parts.filter((p: any) => p.type === "text")
+ const responseText = textParts.map((p: any) => p.text).join("\n")
+
+ log(`[omo_task] Got response, length: ${responseText.length}`)
+
+ const output =
+ responseText + "\n\n" + ["", `session_id: ${sessionID}`, ""].join("\n")
+
+ return output
+ },
+ })
+}
diff --git a/src/tools/omo-task/types.ts b/src/tools/omo-task/types.ts
new file mode 100644
index 0000000..3af015b
--- /dev/null
+++ b/src/tools/omo-task/types.ts
@@ -0,0 +1,26 @@
+import type { ALLOWED_AGENTS } from "./constants"
+
+export type AllowedAgentType = (typeof ALLOWED_AGENTS)[number]
+
+export interface OmoTaskArgs {
+ description: string
+ prompt: string
+ subagent_type: string
+ session_id?: string
+}
+
+export interface OmoTaskResult {
+ title: string
+ metadata: {
+ summary?: Array<{
+ id: string
+ tool: string
+ state: {
+ status: string
+ title?: string
+ }
+ }>
+ sessionId: string
+ }
+ output: string
+}