Skip to main content
Shannon’s workspace system enables crash recovery and resume functionality through git-based checkpointing. This page explains how workspaces work and how to resume interrupted or failed runs.

What is a Workspace?

A workspace is a named directory in audit-logs/ that stores all state for a penetration test run:
audit-logs/
└── example-com_shannon-1234567890/  # Workspace directory
    ├── session.json                  # Session metadata and metrics
    ├── workflow.log                  # Unified human-readable log
    ├── agents/                       # Per-agent execution logs
    ├── prompts/                      # Prompt snapshots
    └── deliverables/                 # Security reports

Workspace Naming

Workspaces are named automatically or explicitly:
Format: {hostname}_{workflowId}Example:
./shannon start URL=https://example.com REPO=my-repo
# Creates: audit-logs/example-com_shannon-1771007534808/
Components:
  • hostname: Sanitized domain from target URL
  • workflowId: Temporal workflow ID (timestamp-based)

Session Metadata

Every workspace contains session.json with comprehensive tracking:
{
  "session": {
    "id": "example-com_shannon-1234567890",
    "webUrl": "https://example.com",
    "repoPath": "./repos/my-repo",
    "status": "in-progress",
    "createdAt": "2025-03-03T10:00:00.000Z",
    "completedAt": null,
    "originalWorkflowId": "shannon-1234567890",
    "checkpointHash": "abc123def456...",
    "resume_history": [
      {
        "timestamp": "2025-03-03T11:30:00.000Z",
        "workflow_id": "shannon-1234567891",
        "terminated_workflows": ["shannon-1234567890"],
        "checkpoint_hash": "abc123def456...",
        "completed_agents": ["pre-recon", "recon", "injection-vuln"]
      }
    ]
  },
  "metrics": {
    "total_cost_usd": 12.34,
    "total_duration_ms": 3600000,
    "phases": { /* ... */ },
    "agents": {
      "pre-recon": {
        "status": "completed",
        "attempts": 1,
        "cost_usd": 2.50,
        "duration_ms": 120000,
        "input_tokens": 50000,
        "output_tokens": 15000,
        "num_turns": 45,
        "started_at": "2025-03-03T10:00:00.000Z",
        "completed_at": "2025-03-03T10:02:00.000Z"
      }
    }
  }
}
Key fields:
  • checkpointHash — Git commit SHA for deliverables
  • originalWorkflowId — First workflow ID that created this workspace
  • resume_history — Array of all resume attempts
  • agents.{name}.status — Agent completion state

Git Checkpointing

Shannon uses git commits to create crash-safe checkpoints after each agent completes.

How Checkpointing Works

1

Agent Completion

After an agent successfully produces its deliverable, Shannon commits the files:
cd repos/my-repo
git add deliverables/{agent}_*.md
git commit -m "[Shannon] {agent} completed"
Location: src/services/git-manager.ts
2

Checkpoint Hash

The commit SHA is saved to session.json:
{
  "session": {
    "checkpointHash": "abc123def456..."
  }
}
3

Concurrency Safety

Git operations are serialized via GitSemaphore to prevent index.lock conflicts during parallel agent execution.Implementation: src/services/git-manager.ts:84

Example Checkpoint Log

$ cd repos/my-repo
$ git log --oneline
abc123d [Shannon] report completed
def456e [Shannon] authz-exploit completed
789abc1 [Shannon] ssrf-exploit completed
...
123def4 [Shannon] recon completed
456abc7 [Shannon] pre-recon completed

Git Repository Requirements

The target repository in repos/ must be a git repository. Shannon uses git for checkpointing. If the directory is not a git repo, initialize one:
cd repos/my-repo
git init
git add .
git commit -m "Initial commit"
Validation: src/services/git-manager.ts:16

Resume Functionality

When to Resume

Resume is useful when:

Crash Recovery

Worker crashed or container restarted

Rate Limits

Hit API rate limits and exhausted retries

Partial Completion

Some agents completed before failure

Incremental Testing

Run phases incrementally for debugging

How Resume Works

Resume is fully automatic — just rerun the same command with WORKSPACE=:
# Original run (failed after recon)
./shannon start URL=https://example.com REPO=my-repo WORKSPACE=my-audit

# Resume (same command)
./shannon start URL=https://example.com REPO=my-repo WORKSPACE=my-audit
Shannon detects completed agents from session.json and skips them.

Resume State Loading

1

Workspace Detection

Function: loadResumeState() in src/temporal/activities.tsChecks if workspace exists in audit-logs/:
const workspacePath = path.join(
  auditLogsDir,
  resumeFromWorkspace
);

if (!await fs.pathExists(workspacePath)) {
  throw new Error(`Workspace not found: ${resumeFromWorkspace}`);
}
2

Session Validation

Loads session.json and validates:
  • Workspace exists
  • URL matches original target (prevents cross-target contamination)
  • Checkpoint hash is valid git commit
const sessionData = await readJson<SessionJson>(sessionPath);

if (sessionData.session.webUrl !== webUrl) {
  throw new PentestError(
    `URL mismatch: workspace was for ${sessionData.session.webUrl}`,
    'validation',
    false,
    {},
    ErrorCode.INVALID_TARGET_ERROR
  );
}
Location: src/temporal/activities.ts:340
3

Deliverable Cross-Check

For each completed agent in session.json, verify the deliverable file exists:
const deliverablePath = path.join(
  repoPath,
  'deliverables',
  agentDef.deliverableFilename
);

if (!await fileExists(deliverablePath)) {
  logger.warn(
    `Agent ${agent} marked complete but deliverable missing. ` +
    `Will re-run.`
  );
  continue; // Don't mark as completed
}
This handles edge cases where session.json was written but deliverable wasn’t committed.
4

Return Resume State

Returns validated state to workflow:
return {
  workspaceName: resumeFromWorkspace,
  originalUrl: sessionData.session.webUrl,
  completedAgents: validatedAgents,
  checkpointHash: sessionData.session.checkpointHash,
  originalWorkflowId: sessionData.session.originalWorkflowId,
};
Type: ResumeState in src/temporal/shared.ts:20

Git Workspace Restoration

1

Checkout Checkpoint

Function: restoreGitCheckpoint() in src/temporal/activities.tsRestores the repo to the last successful checkpoint:
cd repos/my-repo
git reset --hard {checkpointHash}
This ensures a clean state before resuming.
2

Clean Incomplete Deliverables

For agents that didn’t complete, remove any partial deliverables:
for (const agent of incompleteAgents) {
  const agentDef = AGENTS[agent];
  const deliverablePath = path.join(
    repoPath,
    'deliverables',
    agentDef.deliverableFilename
  );
  
  if (await fileExists(deliverablePath)) {
    await fs.remove(deliverablePath);
    logger.info(`Removed incomplete deliverable: ${agent}`);
  }
}
This prevents agents from skipping execution due to stale files.
Location: src/temporal/activities.ts:378

Resume Logging

Every resume attempt is recorded in session.json:
{
  "session": {
    "resume_history": [
      {
        "timestamp": "2025-03-03T12:00:00.000Z",
        "workflow_id": "shannon-1234567891",
        "terminated_workflows": ["shannon-1234567890"],
        "checkpoint_hash": "abc123def456",
        "completed_agents": [
          "pre-recon",
          "recon",
          "injection-vuln",
          "xss-vuln"
        ]
      }
    ]
  }
}
Function: recordResumeAttempt() in src/temporal/activities.ts:429 This also logs to workflow.log:
[2025-03-03 12:00:00] === RESUME ===
[2025-03-03 12:00:00] Workspace: my-audit
[2025-03-03 12:00:00] Checkpoint: abc123def456
[2025-03-03 12:00:00] Completed agents: pre-recon, recon, injection-vuln, xss-vuln
[2025-03-03 12:00:00] Skipping 4 agents, running 9 remaining

Workflow Integration

The resume flow is integrated into the main workflow at src/temporal/workflows.ts:186:
let resumeState: ResumeState | null = null;

if (input.resumeFromWorkspace) {
  // 1. Load resume state
  resumeState = await a.loadResumeState(
    input.resumeFromWorkspace,
    input.webUrl,
    input.repoPath
  );
  
  // 2. Restore git checkpoint
  const incompleteAgents = ALL_AGENTS.filter(
    (agentName) => !resumeState!.completedAgents.includes(agentName)
  );
  
  await a.restoreGitCheckpoint(
    input.repoPath,
    resumeState.checkpointHash,
    incompleteAgents
  );
  
  // 3. Short-circuit if all completed
  if (resumeState.completedAgents.length === ALL_AGENTS.length) {
    state.status = 'completed';
    state.completedAgents = [...resumeState.completedAgents];
    return state;
  }
  
  // 4. Record resume attempt
  await a.recordResumeAttempt(
    activityInput,
    input.terminatedWorkflows || [],
    resumeState.checkpointHash,
    resumeState.originalWorkflowId,
    resumeState.completedAgents
  );
}

// 5. Use shouldSkip() helper throughout workflow
const shouldSkip = (agentName: string): boolean => {
  return resumeState?.completedAgents.includes(agentName) ?? false;
};

Agent Skipping Logic

In each phase, the workflow checks if agents should be skipped:
// Sequential agents
if (!shouldSkip('pre-recon')) {
  state.agentMetrics['pre-recon'] = await a.runPreReconAgent(input);
  state.completedAgents.push('pre-recon');
} else {
  log.info('Skipping pre-recon (already complete)');
  state.completedAgents.push('pre-recon');
}

// Parallel agents
for (const config of pipelineConfigs) {
  if (!shouldSkip(config.vulnAgent) || !shouldSkip(config.exploitAgent)) {
    pipelineThunks.push(
      () => runVulnExploitPipeline(config.vulnType, config.runVuln, config.runExploit)
    );
  } else {
    log.info(`Skipping entire ${config.vulnType} pipeline (both agents complete)`);
    state.completedAgents.push(config.vulnAgent, config.exploitAgent);
  }
}

Listing Workspaces

View all workspaces with the workspaces command:
./shannon workspaces
Output:
=== Shannon Workspaces ===

  WORKSPACE                      URL                            STATUS          DURATION    COST      
  ────────────────────────────────────────────────────────────────────────────────────────────────────
  my-audit                       https://example.com            in-progress     45m         $12.34     (resumable)
  example-com_shannon-1234…      https://example.com            completed       1h 15m      $48.50    
  prod-q1-2025                   https://prod.example.com       failed          30m         $8.20      (resumable)

3 workspaces found (2 resumable)

Resume with: ./shannon start URL=<url> REPO=<repo> WORKSPACE=<name>
Implementation: src/temporal/workspaces.ts

Workspace Status

Workflow is currently running or was interrupted.Resumable: Yes

URL Validation on Resume

Critical safety feature: Shannon enforces URL matching between the original workspace and the resume attempt to prevent cross-target contamination.
if (sessionData.session.webUrl !== webUrl) {
  throw new PentestError(
    `URL mismatch: workspace created for ${sessionData.session.webUrl}, ` +
    `but resume attempt used ${webUrl}`,
    'validation',
    false,
    { workspaceUrl: sessionData.session.webUrl, resumeUrl: webUrl },
    ErrorCode.INVALID_TARGET_ERROR
  );
}
Location: src/temporal/activities.ts:349 Example error:
./shannon start URL=https://prod.example.com REPO=my-repo WORKSPACE=staging-audit

 Error: URL mismatch
   Workspace created for: https://staging.example.com
   Resume attempt used:   https://prod.example.com
   
   This is a safety feature to prevent cross-target contamination.

Edge Cases Handled

Partial Deliverable Corruption

Agent marked as completed in session.json, but deliverable file is missing or corrupted.
Cross-check deliverable existence during loadResumeState():
if (!await fileExists(deliverablePath)) {
  logger.warn(`Deliverable missing for ${agent}. Will re-run.`);
  continue; // Don't include in completedAgents
}
Agent will be re-run on resume.

Multiple Resume Attempts

User resumes multiple times (e.g., after multiple failures).
All resume attempts are logged in resume_history array with timestamps, workflow IDs, and completed agents.The workflow always uses the latest checkpoint hash and completed agents list.

Terminated Workflows

Previous workflow is still running when user starts a resume.
Shannon CLI terminates conflicting workflows before starting resume:
./shannon start URL=https://example.com REPO=my-repo WORKSPACE=my-audit

 Found running workflow: shannon-1234567890
 Terminated and starting resume...
Terminated workflow IDs are recorded in session.json.resume_history[].terminated_workflows.

Parallel Agent Failures

During Phase 3 (5 parallel vuln agents), 2 agents complete and 3 fail.
Only the 2 completed agents are checkpointed. On resume:
  • The 2 completed agents are skipped
  • The 3 failed agents are re-run
  • Parallel execution resumes normally
Location: src/temporal/workflows.ts:436

Best Practices

Use Named Workspaces

For production audits, use semantic names:
WORKSPACE=prod-q1-2025
WORKSPACE=staging-pre-launch

Monitor Progress

Use ./shannon logs and Temporal UI to monitor:
./shannon logs
open http://localhost:8233

Clean Old Workspaces

Periodically remove old workspaces:
rm -rf audit-logs/old-workspace-name

Backup Workspaces

Archive completed workspaces for compliance:
tar -czf prod-q1-2025.tar.gz audit-logs/prod-q1-2025/

Next Steps

Architecture

Understand the underlying system design

Pipeline Phases

Learn about the 5-phase execution model

Agents

Explore the 13 security agents

Configuration

Customize workspace output paths

Build docs developers (and LLMs) love