diff --git a/src/tools/agent-teams/config-tools.test.ts b/src/tools/agent-teams/config-tools.test.ts
new file mode 100644
index 00000000..fadeec00
--- /dev/null
+++ b/src/tools/agent-teams/config-tools.test.ts
@@ -0,0 +1,87 @@
+///
+import { afterEach, beforeEach, describe, expect, test } from "bun:test"
+import { mkdtempSync, rmSync } from "node:fs"
+import { tmpdir } from "node:os"
+import { join } from "node:path"
+import { randomUUID } from "node:crypto"
+import { createTeamConfig, deleteTeamData } from "./team-config-store"
+import { createReadConfigTool } from "./config-tools"
+
+describe("read_config tool", () => {
+ let originalCwd: string
+ let tempProjectDir: string
+ let teamName: string
+ const TEST_SESSION_ID = "test-session-123"
+ const TEST_ABORT_CONTROLLER = new AbortController()
+ const TEST_CONTEXT = {
+ sessionID: TEST_SESSION_ID,
+ messageID: "test-message-123",
+ agent: "test-agent",
+ abort: TEST_ABORT_CONTROLLER.signal,
+ }
+
+ beforeEach(() => {
+ originalCwd = process.cwd()
+ tempProjectDir = mkdtempSync(join(tmpdir(), "agent-teams-config-tools-"))
+ process.chdir(tempProjectDir)
+ teamName = `test-team-${randomUUID()}`
+ })
+
+ afterEach(() => {
+ try {
+ deleteTeamData(teamName)
+ } catch {
+ // ignore
+ }
+ process.chdir(originalCwd)
+ rmSync(tempProjectDir, { recursive: true, force: true })
+ })
+
+ describe("read config action", () => {
+ test("returns team config when team exists", async () => {
+ //#given
+ const config = createTeamConfig(teamName, "Test team", TEST_SESSION_ID, "/tmp", "claude-opus-4-6")
+ const tool = createReadConfigTool()
+
+ //#when
+ const resultStr = await tool.execute({
+ team_name: teamName,
+ }, TEST_CONTEXT)
+ const result = JSON.parse(resultStr)
+
+ //#then
+ expect(result.name).toBe(teamName)
+ expect(result.description).toBe("Test team")
+ expect(result.members).toHaveLength(1)
+ expect(result.members[0].name).toBe("team-lead")
+ expect(result.members[0].agentType).toBe("team-lead")
+ })
+
+ test("returns error for non-existent team", async () => {
+ //#given
+ const tool = createReadConfigTool()
+
+ //#when
+ const resultStr = await tool.execute({
+ team_name: "nonexistent-team-12345",
+ }, TEST_CONTEXT)
+ const result = JSON.parse(resultStr)
+
+ //#then
+ expect(result).toHaveProperty("error")
+ expect(result.error).toBe("team_not_found")
+ })
+
+ test("requires team_name parameter", async () => {
+ //#given
+ const tool = createReadConfigTool()
+
+ //#when
+ const resultStr = await tool.execute({}, TEST_CONTEXT)
+ const result = JSON.parse(resultStr)
+
+ //#then
+ expect(result).toHaveProperty("error")
+ })
+ })
+})
\ No newline at end of file
diff --git a/src/tools/agent-teams/config-tools.ts b/src/tools/agent-teams/config-tools.ts
new file mode 100644
index 00000000..649fd878
--- /dev/null
+++ b/src/tools/agent-teams/config-tools.ts
@@ -0,0 +1,24 @@
+import { tool, type ToolDefinition } from "@opencode-ai/plugin/tool"
+import { readTeamConfig } from "./team-config-store"
+import { ReadConfigInputSchema } from "./types"
+
+export function createReadConfigTool(): ToolDefinition {
+ return tool({
+ description: "Read team configuration and member list.",
+ args: {
+ team_name: tool.schema.string().describe("Team name"),
+ },
+ execute: async (args: Record): Promise => {
+ try {
+ const input = ReadConfigInputSchema.parse(args)
+ const config = readTeamConfig(input.team_name)
+ if (!config) {
+ return JSON.stringify({ error: "team_not_found" })
+ }
+ return JSON.stringify(config)
+ } catch (error) {
+ return JSON.stringify({ error: error instanceof Error ? error.message : "read_config_failed" })
+ }
+ },
+ })
+}
\ No newline at end of file
diff --git a/src/tools/agent-teams/inbox-tools.test.ts b/src/tools/agent-teams/inbox-tools.test.ts
new file mode 100644
index 00000000..ccc4cb51
--- /dev/null
+++ b/src/tools/agent-teams/inbox-tools.test.ts
@@ -0,0 +1,182 @@
+///
+import { afterEach, beforeEach, describe, expect, test } from "bun:test"
+import { mkdtempSync, rmSync } from "node:fs"
+import { tmpdir } from "node:os"
+import { join } from "node:path"
+import { randomUUID } from "node:crypto"
+import { appendInboxMessage, ensureInbox } from "./inbox-store"
+import { deleteTeamData } from "./team-config-store"
+import { createReadInboxTool } from "./inbox-tools"
+
+describe("read_inbox tool", () => {
+ let originalCwd: string
+ let tempProjectDir: string
+ let teamName: string
+ const TEST_SESSION_ID = "test-session-123"
+ const TEST_ABORT_CONTROLLER = new AbortController()
+ const TEST_CONTEXT = {
+ sessionID: TEST_SESSION_ID,
+ messageID: "test-message-123",
+ agent: "test-agent",
+ abort: TEST_ABORT_CONTROLLER.signal,
+ }
+
+ beforeEach(() => {
+ originalCwd = process.cwd()
+ tempProjectDir = mkdtempSync(join(tmpdir(), "agent-teams-inbox-tools-"))
+ process.chdir(tempProjectDir)
+ teamName = `test-team-${randomUUID()}`
+ })
+
+ afterEach(() => {
+ try {
+ deleteTeamData(teamName)
+ } catch {
+ // ignore
+ }
+ process.chdir(originalCwd)
+ rmSync(tempProjectDir, { recursive: true, force: true })
+ })
+
+ afterEach(() => {
+ process.chdir(originalCwd)
+ rmSync(tempProjectDir, { recursive: true, force: true })
+ })
+
+ describe("read inbox action", () => {
+ test("returns all messages when no filters", async () => {
+ //#given
+ ensureInbox(teamName, "team-lead")
+ appendInboxMessage(teamName, "team-lead", {
+ id: "msg-1",
+ type: "message",
+ sender: "user",
+ recipient: "team-lead",
+ content: "Hello",
+ timestamp: new Date().toISOString(),
+ read: false,
+ })
+ appendInboxMessage(teamName, "team-lead", {
+ id: "msg-2",
+ type: "message",
+ sender: "user",
+ recipient: "team-lead",
+ content: "World",
+ timestamp: new Date().toISOString(),
+ read: true,
+ })
+
+ const tool = createReadInboxTool()
+
+ //#when
+ const resultStr = await tool.execute({
+ team_name: teamName,
+ agent_name: "team-lead",
+ }, TEST_CONTEXT)
+ const result = JSON.parse(resultStr)
+
+ //#then
+ expect(result).toHaveLength(2)
+ expect(result[0].id).toBe("msg-1")
+ expect(result[1].id).toBe("msg-2")
+ })
+
+ test("returns only unread messages when unread_only is true", async () => {
+ //#given
+ ensureInbox(teamName, "team-lead")
+ appendInboxMessage(teamName, "team-lead", {
+ id: "msg-1",
+ type: "message",
+ sender: "user",
+ recipient: "team-lead",
+ content: "Hello",
+ timestamp: new Date().toISOString(),
+ read: false,
+ })
+ appendInboxMessage(teamName, "team-lead", {
+ id: "msg-2",
+ type: "message",
+ sender: "user",
+ recipient: "team-lead",
+ content: "World",
+ timestamp: new Date().toISOString(),
+ read: true,
+ })
+
+ const tool = createReadInboxTool()
+
+ //#when
+ const resultStr = await tool.execute({
+ team_name: teamName,
+ agent_name: "team-lead",
+ unread_only: true,
+ }, TEST_CONTEXT)
+ const result = JSON.parse(resultStr)
+
+ //#then
+ expect(result).toHaveLength(1)
+ expect(result[0].id).toBe("msg-1")
+ })
+
+ test("marks messages as read when mark_as_read is true", async () => {
+ //#given
+ ensureInbox(teamName, "team-lead")
+ appendInboxMessage(teamName, "team-lead", {
+ id: "msg-1",
+ type: "message",
+ sender: "user",
+ recipient: "team-lead",
+ content: "Hello",
+ timestamp: new Date().toISOString(),
+ read: false,
+ })
+
+ const tool = createReadInboxTool()
+
+ //#when
+ await tool.execute({
+ team_name: teamName,
+ agent_name: "team-lead",
+ mark_as_read: true,
+ }, TEST_CONTEXT)
+
+ // Read again to check if marked as read
+ const resultStr = await tool.execute({
+ team_name: teamName,
+ agent_name: "team-lead",
+ unread_only: true,
+ }, TEST_CONTEXT)
+ const result = JSON.parse(resultStr)
+
+ //#then
+ expect(result).toHaveLength(0) // Should be marked as read
+ })
+
+ test("returns empty array for non-existent inbox", async () => {
+ //#given
+ const tool = createReadInboxTool()
+
+ //#when
+ const resultStr = await tool.execute({
+ team_name: "nonexistent",
+ agent_name: "team-lead",
+ }, TEST_CONTEXT)
+ const result = JSON.parse(resultStr)
+
+ //#then
+ expect(result).toEqual([])
+ })
+
+ test("requires team_name and agent_name parameters", async () => {
+ //#given
+ const tool = createReadInboxTool()
+
+ //#when
+ const resultStr = await tool.execute({}, TEST_CONTEXT)
+ const result = JSON.parse(resultStr)
+
+ //#then
+ expect(result).toHaveProperty("error")
+ })
+ })
+})
\ No newline at end of file
diff --git a/src/tools/agent-teams/inbox-tools.ts b/src/tools/agent-teams/inbox-tools.ts
new file mode 100644
index 00000000..fe8de3e9
--- /dev/null
+++ b/src/tools/agent-teams/inbox-tools.ts
@@ -0,0 +1,29 @@
+import { tool, type ToolDefinition } from "@opencode-ai/plugin/tool"
+import { readInbox } from "./inbox-store"
+import { ReadInboxInputSchema } from "./types"
+
+export function createReadInboxTool(): ToolDefinition {
+ return tool({
+ description: "Read inbox messages for a team member.",
+ args: {
+ team_name: tool.schema.string().describe("Team name"),
+ agent_name: tool.schema.string().describe("Member name"),
+ unread_only: tool.schema.boolean().optional().describe("Return only unread messages"),
+ mark_as_read: tool.schema.boolean().optional().describe("Mark returned messages as read"),
+ },
+ execute: async (args: Record): Promise => {
+ try {
+ const input = ReadInboxInputSchema.parse(args)
+ const messages = readInbox(
+ input.team_name,
+ input.agent_name,
+ input.unread_only ?? false,
+ input.mark_as_read ?? false,
+ )
+ return JSON.stringify(messages)
+ } catch (error) {
+ return JSON.stringify({ error: error instanceof Error ? error.message : "read_inbox_failed" })
+ }
+ },
+ })
+}
\ No newline at end of file