diff --git a/src/features/team-mode/team-mailbox/ack.test.ts b/src/features/team-mode/team-mailbox/ack.test.ts
new file mode 100644
index 000000000..4c337c1dd
--- /dev/null
+++ b/src/features/team-mode/team-mailbox/ack.test.ts
@@ -0,0 +1,45 @@
+///
+
+import { describe, expect, test } from "bun:test"
+import { mkdtemp, readdir } from "node:fs/promises"
+import { randomUUID } from "node:crypto"
+import { tmpdir } from "node:os"
+import path from "node:path"
+
+import { TeamModeConfigSchema } from "../../../config/schema/team-mode"
+import { getInboxDir, resolveBaseDir } from "../team-registry/paths"
+import { ackMessages } from "./ack"
+import { sendMessage } from "./send"
+
+async function createBaseDirectory(): Promise {
+ return await mkdtemp(path.join(tmpdir(), "team-mailbox-ack-"))
+}
+
+describe("ackMessages", () => {
+ test("moves inbox files into processed and stays idempotent", async () => {
+ // given
+ const config = TeamModeConfigSchema.parse({ base_dir: await createBaseDirectory() })
+ const teamRunId = randomUUID()
+ const messageId = randomUUID()
+ await sendMessage({
+ version: 1,
+ messageId,
+ from: "lead",
+ to: "m1",
+ kind: "message",
+ body: "hello",
+ timestamp: 100,
+ }, teamRunId, config, { isLead: true, activeMembers: ["m1"] })
+
+ // when
+ await ackMessages(teamRunId, "m1", [messageId], config)
+ await ackMessages(teamRunId, "m1", [messageId], config)
+
+ // then
+ const inboxDir = getInboxDir(resolveBaseDir(config), teamRunId, "m1")
+ const inboxEntries = await readdir(inboxDir)
+ const processedEntries = await readdir(path.join(inboxDir, "processed"))
+ expect(inboxEntries).not.toContain(`${messageId}.json`)
+ expect(processedEntries).toContain(`${messageId}.json`)
+ })
+})
diff --git a/src/features/team-mode/team-mailbox/ack.ts b/src/features/team-mode/team-mailbox/ack.ts
new file mode 100644
index 000000000..9428f7ae5
--- /dev/null
+++ b/src/features/team-mode/team-mailbox/ack.ts
@@ -0,0 +1,34 @@
+import { mkdir, rename } from "node:fs/promises"
+import path from "node:path"
+
+import type { TeamModeConfig } from "../../../config/schema/team-mode"
+import { getInboxDir, resolveBaseDir } from "../team-registry/paths"
+
+export async function ackMessages(
+ teamRunId: string,
+ memberName: string,
+ messageIds: string[],
+ config: TeamModeConfig,
+): Promise {
+ const baseDir = resolveBaseDir(config)
+ const inboxDir = getInboxDir(baseDir, teamRunId, memberName)
+ const processedDir = path.join(inboxDir, "processed")
+ await mkdir(processedDir, { recursive: true, mode: 0o700 })
+
+ for (const messageId of messageIds) {
+ const messageFileName = `${messageId}.json`
+ const sourcePath = path.join(inboxDir, messageFileName)
+ const targetPath = path.join(processedDir, messageFileName)
+
+ try {
+ await rename(sourcePath, targetPath)
+ } catch (error) {
+ const err = error as NodeJS.ErrnoException
+ if (err.code === "ENOENT") {
+ continue
+ }
+
+ throw error
+ }
+ }
+}