diff --git a/package.json b/package.json
index a40b4796..5dc5e7b0 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "opencode-forge",
-  "version": "0.2.3",
+  "version": "0.2.4",
   "type": "module",
   "oc-plugin": [
     "server",
diff --git a/src/agents/index.ts b/src/agents/index.ts
index 7bbf23ca..0228d5a9 100644
--- a/src/agents/index.ts
+++ b/src/agents/index.ts
@@ -9,4 +9,15 @@ export const agents: Record<AgentRole, AgentDefinition> = {
   auditor: auditorAgent,
 }
 
+/**
+ * Returns the list of tools that the given agent role is configured to exclude.
+ *
+ * Callers use this to append the exclusions as deny rules when constructing a
+ * loop session's permission ruleset, so the agent cannot invoke those tools
+ * regardless of the allow-all worktree rule.
+ */
+export function getAgentExcludedTools(role: AgentRole = 'code'): string[] {
+  return agents[role]?.tools?.exclude ?? []
+}
+
 export { type AgentRole, type AgentDefinition, type AgentConfig } from './types'
diff --git a/src/cli/commands/restart.ts b/src/cli/commands/restart.ts
index 4bf82dc7..bdcbe710 100644
--- a/src/cli/commands/restart.ts
+++ b/src/cli/commands/restart.ts
@@ -1,5 +1,6 @@
 import type { LoopState } from '../../services/loop'
 import { buildLoopPermissionRuleset } from '../../constants/loop'
+import { getAgentExcludedTools } from '../../agents'
 import {
   openDatabase,
   confirm,
@@ -109,10 +110,13 @@ export async function run(argv: RestartArgs): Promise<void> {
       }
     }
 
-    // Worktree sessions no longer need log directory access since logging is dispatched via host session
+    // Worktree sessions no longer need log directory access since logging is dispatched via host session.
+    // Forward the agent's excluded tools as deny rules so a CLI-restarted loop retains the same
+    // tool exclusions as a freshly launched loop.
     const config = loadPluginConfig()
     const permissionRuleset = buildLoopPermissionRuleset(config, null, {
       isWorktree: !!state.worktree,
+      excludedTools: getAgentExcludedTools('code'),
     })
 
     console.log(`restart: creating session with directory=${sessionDir} (sandbox: ${!!state.sandbox})`)
diff --git a/src/constants/loop.ts b/src/constants/loop.ts
index eed73413..7986fdc0 100644
--- a/src/constants/loop.ts
+++ b/src/constants/loop.ts
@@ -10,19 +10,23 @@ type PermissionRule = { permission: string; pattern: string; action: 'allow' | '
  * - Adds external_directory allow rule for worktree logging when configured AND needed.
  *   Note: With host-session dispatch, worktree sessions no longer need direct host log access.
  *   This parameter is kept for backward compatibility but should be null for new designs.
+ * - Agent tool exclusions are appended as deny rules at the END to ensure they take precedence.
  *
  * Note on external_directory evaluation: The blanket `*:*:allow` for worktree loops
  * covers the session's own cwd. The `external_directory:*:deny` rule only blocks
  * paths outside the worktree. Audit performed: sandbox worktree loops launch
  * without permission prompts for their own cwd because the container-mapped
  * directory falls within the worktree scope that the blanket allow covers.
+ *
+ * @param excludedTools - List of tool names to exclude (from agent definition). These are appended as deny rules last.
  */
 export function buildLoopPermissionRuleset(
   config: PluginConfig,
   logDirectory?: string | null,
-  options?: { isWorktree?: boolean },
+  options?: { isWorktree?: boolean; excludedTools?: string[] },
 ): PermissionRule[] {
   const isWorktree = options?.isWorktree ?? true
+  const excludedTools = options?.excludedTools ?? []
   const rules: PermissionRule[] = []
 
   if (isWorktree) {
@@ -50,5 +54,11 @@ export function buildLoopPermissionRuleset(
     { permission: 'loop-status', pattern: '*', action: 'deny' },
   )
 
+  // Append agent tool exclusions as deny rules at the END
+  // This ensures they take precedence due to findLast evaluation in opencode
+  for (const tool of excludedTools) {
+    rules.push({ permission: tool, pattern: '*', action: 'deny' })
+  }
+
   return rules
 }
diff --git a/src/hooks/loop.ts b/src/hooks/loop.ts
index 375adccf..56593ad1 100644
--- a/src/hooks/loop.ts
+++ b/src/hooks/loop.ts
@@ -11,6 +11,7 @@ import { buildWorktreeCompletionPayload, writeWorktreeCompletionLog } from '../s
 import { buildLoopPermissionRuleset } from '../constants/loop'
 import { createLoopSessionWithWorkspace, publishWorkspaceDetachedToast } from '../utils/loop-session'
 import { cleanupLoopWorktree } from '../utils/worktree-cleanup'
+import { getAgentExcludedTools } from '../agents'
 
 export interface LoopEventHandler {
   onEvent(input: { event: { type: string; properties?: Record<string, unknown> } }): Promise<void>
@@ -411,9 +412,12 @@ export function createLoopEventHandler(
     const sessionDir = state.worktreeDir
 
     // Worktree sessions no longer need log directory access since logging is dispatched via host session
-    // Only resolve log target for non-worktree sessions
+    // Only resolve log target for non-worktree sessions.
+    // Forward the agent's excluded tools as deny rules so they remain enforced across every
+    // iteration's rotated session, not just iteration 1.
     const permissionRuleset = buildLoopPermissionRuleset(getConfig(), null, {
       isWorktree: !!state.worktree,
+      excludedTools: getAgentExcludedTools('code'),
     })
 
     const createResult = await createLoopSessionWithWorkspace({
diff --git a/src/services/loop.ts b/src/services/loop.ts
index 923f661e..ba603d35 100644
--- a/src/services/loop.ts
+++ b/src/services/loop.ts
@@ -244,7 +244,7 @@ export function createLoopService(
         `- ${finding.file}:${finding.line}`,
         `  - Severity: ${finding.severity}`,
         `  - Description: ${finding.description}`,
-        `  - Scenario: ${finding.scenario}`,
+        `  - Scenario: ${finding.scenario || 'N/A'}`,
       ].join('\n')
     }).join('\n\n')
   }
diff --git a/src/storage/database.ts b/src/storage/database.ts
index 3de0efa8..82941308 100644
--- a/src/storage/database.ts
+++ b/src/storage/database.ts
@@ -64,6 +64,13 @@ interface ForgeDatabaseOptions {
 
 const DEFAULT_COMPLETED_LOOP_TTL_MS = 7 * 24 * 60 * 60 * 1000
 
+type CachedDb = { db: Database; refCount: number }
+const dbCache = new Map<string, CachedDb>()
+
+function cacheKey(dbPath: string, options?: ForgeDatabaseOptions): string {
+  return `${dbPath}::${options?.completedLoopTtlMs ?? DEFAULT_COMPLETED_LOOP_TTL_MS}`
+}
+
 export function openForgeDatabase(dbPath: string, options?: ForgeDatabaseOptions): Database {
   const db = openSqliteWithIntegrityGuard(dbPath, {
     label: 'Forge database',
@@ -84,10 +91,26 @@ export function initializeDatabase(dataDir: string, options?: ForgeDatabaseOptio
   }
 
   const dbPath = `${dataDir}/graph.db`
-  
-  return openForgeDatabase(dbPath, options)
+  const key = cacheKey(dbPath, options)
+  const cached = dbCache.get(key)
+  if (cached) {
+    cached.refCount += 1
+    return cached.db
+  }
+  const db = openForgeDatabase(dbPath, options)
+  dbCache.set(key, { db, refCount: 1 })
+  return db
 }
 
 export function closeDatabase(db: Database): void {
+  for (const [key, entry] of dbCache) {
+    if (entry.db !== db) continue
+    entry.refCount -= 1
+    if (entry.refCount <= 0) {
+      dbCache.delete(key)
+      db.close()
+    }
+    return
+  }
   db.close()
 }
diff --git a/src/storage/migrations/index.ts b/src/storage/migrations/index.ts
index b6929da3..67923ffa 100644
--- a/src/storage/migrations/index.ts
+++ b/src/storage/migrations/index.ts
@@ -96,4 +96,11 @@ export const migrations: Migration[] = [
       db.run(loadSql('110_drop_completion_signal_from_loops.sql'))
     },
   },
+  {
+    id: '111',
+    description: 'Make scenario column nullable in review_findings table',
+    apply: (db: Database) => {
+      db.run(loadSql('111_make_scenario_nullable.sql'))
+    },
+  },
 ]
diff --git a/src/tools/loop.ts b/src/tools/loop.ts
index 6c39b4a7..32d015b9 100644
--- a/src/tools/loop.ts
+++ b/src/tools/loop.ts
@@ -17,6 +17,7 @@ import { waitForGraphReady } from '../utils/tui-graph-status'
 import { createLoopWorkspace } from '../workspace/forge-worktree'
 import { createLoopSessionWithWorkspace, publishWorkspaceDetachedToast } from '../utils/loop-session'
 import { cleanupLoopWorktree } from '../utils/worktree-cleanup'
+import { getAgentExcludedTools } from '../agents'
 
 const z = tool.schema
 
@@ -66,8 +67,10 @@ export async function setupLoop(
       sandbox: false,
       dataDir: ctx.dataDir,
     }, ctx.logger)
+    // Get the agent's excluded tools to enforce as permanent denials
     const permissionRuleset = buildLoopPermissionRuleset(config, logTarget?.permissionPath ?? null, {
       isWorktree: false,
+      excludedTools: getAgentExcludedTools('code'),
     })
 
     let currentBranch: string | undefined
@@ -152,8 +155,10 @@ export async function setupLoop(
 
     // Worktree sessions no longer need log directory access since logging is dispatched via host session
     // Only resolve log target for non-worktree sessions or if needed for other purposes
+    // Get the agent's excluded tools to enforce as permanent denials
     const permissionRuleset = buildLoopPermissionRuleset(config, null, {
       isWorktree: true,
+      excludedTools: getAgentExcludedTools('code'),
     })
 
     logger.log(`loop: creating session with directory=${sessionDirectory} (host: ${hostWorktreeDir}, sandbox: ${sandboxEnabled})`)
@@ -508,8 +513,10 @@ export function createLoopTools(ctx: ToolContext): Record<string, ReturnType<typ
           }
 
           // Worktree sessions no longer need log directory access since logging is dispatched via host session
+          // Get the agent's excluded tools to enforce as permanent denials
           const permissionRuleset = buildLoopPermissionRuleset(config, null, {
             isWorktree: !!stoppedState.worktree,
+            excludedTools: getAgentExcludedTools('code'),
           })
 
           const restartSandbox = isSandboxEnabled(config, ctx.sandboxManager)
diff --git a/src/tui.tsx b/src/tui.tsx
index d201e171..fc0de984 100644
--- a/src/tui.tsx
+++ b/src/tui.tsx
@@ -21,6 +21,7 @@ import { getGitProjectId } from './utils/project-id'
 import { formatDuration, formatTokens, truncate, truncateMiddle } from './utils/format'
 
 import { buildLoopPermissionRuleset } from './constants/loop'
+import { getAgentExcludedTools } from './agents'
 import { createLoopSessionWithWorkspace } from './utils/loop-session'
 
 type TuiKeybinds = {
@@ -158,6 +159,7 @@ async function restartLoop(projectId: string, loopName: string, api: TuiPluginAp
     const config = loadPluginConfig()
     const permissionRuleset = buildLoopPermissionRuleset(config, null, {
       isWorktree: !!row.worktree,
+      excludedTools: getAgentExcludedTools('code'),
     })
 
     // Resolve the workspace ID for worktree loops.
diff --git a/src/utils/loop-launch.ts b/src/utils/loop-launch.ts
index 7475c5cc..51e87cf4 100644
--- a/src/utils/loop-launch.ts
+++ b/src/utils/loop-launch.ts
@@ -21,6 +21,7 @@ import { createLoopsRepo } from '../storage/repos/loops-repo'
 import type { LoopRow, LoopLargeFields } from '../storage/repos/loops-repo'
 import { createLoopWorkspace } from '../workspace/forge-worktree'
 import { createLoopSessionWithWorkspace } from './loop-session'
+import { getAgentExcludedTools } from '../agents'
 
 interface FreshLoopOptions {
   planText: string
@@ -142,8 +143,10 @@ export async function launchFreshLoop(options: FreshLoopOptions): Promise<Launch
     }
 
     // Worktree sessions no longer need log directory access since logging is dispatched via host session
+    // Get the agent's excluded tools to enforce as permanent denials
     const permissionRuleset = buildLoopPermissionRuleset(config, null, {
       isWorktree: true,
+      excludedTools: getAgentExcludedTools('code'),
     })
     
     console.log(`loop-launch: creating session with directory=${hostWorktreeDir} (sandbox: ${isSandboxEnabled})`)
@@ -176,8 +179,10 @@ export async function launchFreshLoop(options: FreshLoopOptions): Promise<Launch
       sandbox: isSandboxEnabled,
       dataDir,
     })
+    // Get the agent's excluded tools to enforce as permanent denials
     const permissionRuleset = buildLoopPermissionRuleset(config, logTarget?.permissionPath ?? null, {
       isWorktree: false,
+      excludedTools: getAgentExcludedTools('code'),
     })
     
     const createResult = await api.client.session.create({
diff --git a/src/version.ts b/src/version.ts
index baf006bd..1e5fbf5d 100644
--- a/src/version.ts
+++ b/src/version.ts
@@ -1 +1 @@
-export const VERSION = '0.2.3'
+export const VERSION = '0.2.4'
