Skip to content
Home
GitHub

Base Skill

Every Expert automatically has access to @perstack/base — a built-in skill that provides file operations, runtime control, and other essential tools.

No configuration needed. It’s always available.

Base Skill is the only skill tightly coupled with @perstack/runtime. The name “base” reflects this fundamental role — Perstack cannot operate without it, and the runtime assumes Base Skill is always present.

This coupling enables MCP-native runtime control. Rather than implementing special control mechanisms outside MCP, runtime operations (task completion, todos) are exposed as standard MCP tools.

For binary files (images, PDFs), Base Skill returns only path and mimeType. The runtime then:

  1. Reads the file from the returned path
  2. Base64 encodes the content
  3. Passes the encoded data to the LLM

This separation keeps Base Skill simple while letting the runtime handle LLM-specific formatting.

Base Skill never accesses external networks. This is intentional:

  • Keeps the attack surface minimal
  • Forces explicit network dependencies via separate skills
  • Makes network access auditable

If your Expert needs network access, define a dedicated skill for it.

All file operations are restricted to the workspace directory (where perstack run was executed).

  • Experts cannot read, write, or access files outside the workspace
  • Path traversal attempts (e.g., ../) are blocked
  • The perstack/ directory is hidden from directory listings

ToolCategoryDescription
attemptCompletionRuntimeSignal task completion
todoRuntimeManage task list
clearTodoRuntimeClear task list
execSystemExecute system commands
readTextFileFileRead text files
readImageFileFileRead image files (PNG, JPEG, GIF, WebP)
readPdfFileFileRead PDF files
writeTextFileFileCreate or overwrite text files
editTextFileFileSearch and replace in text files

Executes system commands within the workspace.

Parameters:

NameTypeRequiredDescription
commandstringYesCommand to execute (e.g., ls, python)
argsstring[]YesArguments to pass to the command
envRecord<string, string>YesEnvironment variables
cwdstringYesWorking directory (must be within workspace)
stdoutbooleanYesWhether to capture stdout
stderrbooleanYesWhether to capture stderr
timeoutnumberNoTimeout in milliseconds

Returns:

{ "output": "command output here" }

Behavior:

  • Executes command using Node.js execFile
  • Validates cwd is within the workspace
  • Merges provided env with process.env
  • Returns “Command executed successfully, but produced no output.” if no output captured
  • Returns timeout error if command exceeds timeout

Constraints:

  • Working directory must be within workspace
  • Do not execute long-running foreground commands (e.g., tail -f)
  • Be cautious with resource-intensive commands

Security Note: While cwd is validated, the executed command itself can still access files outside the workspace (e.g., cat /etc/passwd). For production deployments, use infrastructure-level isolation (containers, sandboxes) to enforce strict boundaries.


Signals task completion with automatic todo validation.

Parameters: None

Returns (remaining todos):

{
"remainingTodos": [
{ "id": 0, "title": "Incomplete task 1", "completed": false },
{ "id": 2, "title": "Incomplete task 2", "completed": false }
]
}

Returns (all todos complete or no todos):

{}

Behavior:

  • Checks the current todo list for incomplete items
  • If incomplete todos exist: returns them in remainingTodos and continues the agent loop
  • If no incomplete todos (or no todos at all): triggers run result generation and ends the agent loop
  • The Expert should complete remaining todos before calling again

Run result:

When todo validation passes, the LLM generates a run result — a summary of the work done. This ensures every completed run has a clear outcome, regardless of whether the Expert was delegated or run directly.

For delegated Experts, the run result is returned to the delegating Expert as the tool call result, maintaining context isolation.

Best Practice:

  • Mark all todos as complete before calling attemptCompletion
  • Use clearTodo if you want to reset and start fresh
  • The tool prevents premature completion by surfacing forgotten tasks

Task list manager for tracking work items.

Parameters:

NameTypeRequiredDescription
newTodosstring[]NoTask descriptions to add
completedTodosnumber[]NoTodo IDs to mark as completed

Returns:

{
"todos": [
{ "id": 0, "title": "Task 1", "completed": false },
{ "id": 1, "title": "Task 2", "completed": true }
]
}

Behavior:

  • Each todo gets a unique incremental ID when created
  • Returns the full todo list after every operation
  • State persists across calls within the session

Observability: The runtime records all tool calls (including todo) into checkpoints, making task progress visible in execution history.


Resets the todo list to empty state.

Parameters: None

Returns:

{ "todos": [] }

Reads text files with optional line range support.

Parameters:

NameTypeRequiredDescription
pathstringYesFile path
fromnumberNoStart line (0-indexed)
tonumberNoEnd line (exclusive)

Returns:

{ "path": "file.txt", "content": "file content", "from": 0, "to": 10 }

Behavior:

  • Reads as UTF-8 encoded text
  • Supports partial reading via line range
  • Defaults to reading entire file if range not specified

Constraints:

  • File must exist
  • Binary files will cause errors or corrupted output

Validates and returns image file metadata for runtime processing.

Parameters:

NameTypeRequiredDescription
pathstringYesImage file path

Returns:

{ "path": "/workspace/image.png", "mimeType": "image/png", "size": 12345 }

Runtime Integration: The runtime reads the file at path, base64 encodes it, and passes it to the LLM as an inline image.

Supported formats: PNG, JPEG, GIF, WebP

Constraints:

  • Maximum file size: 15MB
  • File must exist and be a supported image format

Validates and returns PDF file metadata for runtime processing.

Parameters:

NameTypeRequiredDescription
pathstringYesPDF file path

Returns:

{ "path": "/workspace/doc.pdf", "mimeType": "application/pdf", "size": 54321 }

Runtime Integration: The runtime reads the file at path, base64 encodes it, and passes it to the LLM as an inline file.

Constraints:

  • Maximum file size: 30MB
  • File must exist and be a valid PDF

Creates or overwrites text files.

Parameters:

NameTypeRequiredDescription
pathstringYesTarget file path
textstringYesContent to write (max 10,000 characters)

Returns:

{ "path": "/workspace/file.txt", "text": "written content" }

Behavior:

  • Creates parent directories automatically
  • Overwrites existing files
  • Writes as UTF-8 encoded text
  • Pass empty string to clear file contents

Constraints:

  • Maximum 10,000 characters per call

Performs search-and-replace in text files.

Parameters:

NameTypeRequiredDescription
pathstringYesTarget file path
oldTextstringYesText to find (max 2,000 characters)
newTextstringYesReplacement text (max 2,000 characters)

Returns:

{ "path": "/workspace/file.txt", "oldText": "old", "newText": "new" }

Behavior:

  • Performs exact string replacement (first occurrence only)
  • Normalizes line endings (CRLF → LF) before matching
  • File must contain exact match of oldText

Constraints:

  • File must exist
  • oldText must exist in file
  • Maximum 2,000 characters for both oldText and newText

Here’s how an Expert uses Base Skill tools in a typical agent loop:

┌─────────────────────────────────────────────────────────────────┐
│ User: "Read all source files and write a summary" │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 1. LIST FILES │
│ ───────────────────────────────────────────────────────────── │
│ tool: exec │
│ input: { command: "ls", args: ["src/"], ... } │
│ output: { output: "index.ts\nlib/\nutils/" } │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 2. TODO │
│ ───────────────────────────────────────────────────────────── │
│ tool: todo │
│ input: { newTodos: [ │
│ "Read source files", │
│ "Write summary" │
│ ]} │
│ output: { todos: [{ id: 0, title: "...", completed: false }] } │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 3. READ FILES │
│ ───────────────────────────────────────────────────────────── │
│ tool: readTextFile │
│ input: { path: "src/index.ts" } │
│ output: { content: "..." } │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 4. WRITE SUMMARY │
│ ───────────────────────────────────────────────────────────── │
│ tool: writeTextFile │
│ input: { path: "SUMMARY.md", text: "# Summary\n..." } │
│ output: { path: "/workspace/SUMMARY.md" } │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 5. MARK TODOS COMPLETE │
│ ───────────────────────────────────────────────────────────── │
│ tool: todo │
│ input: { completedTodos: [0, 1] } │
│ output: { todos: [{ id: 0, completed: true }, ...] } │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 6. COMPLETE │
│ ───────────────────────────────────────────────────────────── │
│ tool: attemptCompletion │
│ input: {} │
│ output: {} (no remaining todos) │
│ → Agent loop ends │
└─────────────────────────────────────────────────────────────────┘

The Expert definition for this workflow:

[experts."summarizer"]
description = "Reads source files and writes a summary"
instruction = """
You read all source files and write a summary.
1. List files using exec
2. Read each source file using readTextFile
3. Write a summary using writeTextFile
"""

All tools come from Base Skill — no skill definitions needed.