Skip to content

Skill Packs

ThinkWork implements the Agent Skills specification — an open standard for portable, agent-readable skill definitions. A skill pack is a directory containing a SKILL.md file (with optional scripts/, references/, and assets/ directories) that defines tools, instructions, and context for managed agents.

Skill packs are stored in S3 and loaded by AgentCore at invoke time. You don’t need to rebuild or redeploy the AgentCore Lambda to add new skills — upload a skill pack and assign it to an agent.

1. Author a skill folder with `SKILL.md`
2. Upload the folder to S3: `tenants/<tenant-slug>/skill-catalog/<skill-slug>/`
3. Install the skill from the admin Skills tab
4. The install copies the folder into the target workspace at `skills/<skill-slug>/`
5. At invoke time: AgentCore syncs the workspace, parses `SKILL.md`, and registers the skill

A skill follows the Agent Skills directory structure:

my-skill/
├── SKILL.md # Required: metadata + instructions
├── scripts/ # Optional: executable code
├── references/ # Optional: additional documentation
├── assets/ # Optional: templates, data files
└── skill.yaml # Optional: ThinkWork catalog metadata

The only required file is SKILL.md. The skill.yaml file is a ThinkWork extension for catalog metadata (display name, icon, category, triggers).

Per the Agent Skills spec, SKILL.md has YAML frontmatter followed by markdown content.

---
name: my-skill
description: What this skill does and when to use it.
---
Your instructions here.

Here’s a Jira skill with all sections:

Instructions section — injected into the system prompt:

## Instructions
You have access to Jira. When a user asks about issues, tickets, or tasks,
use the Jira tools to look up real data instead of guessing. Always include
the issue key (e.g., ENG-1234) when referencing issues.

Tools section — Python functions exposed as agent tools:

## Tools
import httpx
import os
JIRA_BASE_URL = os.environ["JIRA_BASE_URL"]
JIRA_TOKEN = os.environ["JIRA_TOKEN"]
JIRA_EMAIL = os.environ["JIRA_EMAIL"]
def search_issues(jql: str, max_results: int = 20) -> dict:
"""
Search Jira issues using JQL.
Args:
jql: JQL query string (e.g., 'project=ENG AND status=Open')
max_results: Maximum number of results to return (default 20, max 100)
Returns:
dict with 'issues' list, each containing id, key, summary, status, assignee
"""
response = httpx.get(
f"{JIRA_BASE_URL}/rest/api/3/search",
headers={
"Authorization": f"Basic {JIRA_EMAIL}:{JIRA_TOKEN}",
"Content-Type": "application/json"
},
params={"jql": jql, "maxResults": max_results}
)
response.raise_for_status()
data = response.json()
return {
"issues": [
{
"id": issue["id"],
"key": issue["key"],
"summary": issue["fields"]["summary"],
"status": issue["fields"]["status"]["name"],
"assignee": issue["fields"].get("assignee", {}).get("displayName", "Unassigned")
}
for issue in data["issues"]
]
}
def create_issue(project_key: str, summary: str, description: str, issue_type: str = "Task") -> dict:
"""
Create a new Jira issue.
Args:
project_key: Jira project key (e.g., 'ENG')
summary: Issue title/summary
description: Issue description (plain text)
issue_type: Issue type (Task, Bug, Story, Epic) — defaults to Task
Returns:
dict with 'id', 'key', and 'url' of the created issue
"""
response = httpx.post(
f"{JIRA_BASE_URL}/rest/api/3/issue",
headers={
"Authorization": f"Basic {JIRA_EMAIL}:{JIRA_TOKEN}",
"Content-Type": "application/json"
},
json={
"fields": {
"project": {"key": project_key},
"summary": summary,
"description": {
"type": "doc", "version": 1,
"content": [{"type": "paragraph", "content": [{"type": "text", "text": description}]}]
},
"issuetype": {"name": issue_type}
}
}
)
response.raise_for_status()
data = response.json()
return {
"id": data["id"],
"key": data["key"],
"url": f"{JIRA_BASE_URL}/browse/{data['key']}"
}

Context section — static text injected into every turn’s context window:

## Context
Supported Jira issue types: Task, Bug, Story, Epic, Subtask
Valid JQL operators: =, !=, IN, NOT IN, ~, IS, IS NOT, <, >, <=, >=
Common JQL fields: project, status, assignee, reporter, priority, labels, created, updated
SectionRequiredPurpose
# <name> + descriptionYesSkill name and one-line description. The name is used as the skill ID.
## InstructionsNoText injected into the system prompt for this skill. Keep it concise — this is added to every turn.
## ToolsNoPython code block defining functions. Each def with a docstring becomes a tool.
## ContextNoStatic text injected into the context window on every turn. Good for reference tables, enum values, and constants.

Tools run inside the AgentCore Lambda container. They have access to environment variables set on the Lambda function. Use this for credentials and configuration:

import os
JIRA_TOKEN = os.environ["JIRA_TOKEN"] # Set on AgentCore Lambda
JIRA_BASE_URL = os.environ["JIRA_BASE_URL"]

Set Lambda environment variables via Terraform:

# In your terraform.tfvars or a .tfvars override:
agentcore_environment = {
JIRA_BASE_URL = "https://yourorg.atlassian.net"
JIRA_EMAIL = "bot@yourorg.com"
JIRA_TOKEN = "your-api-token"
}

For sensitive values, use Secrets Manager and fetch at cold start:

import boto3
import json
_secrets_client = boto3.client("secretsmanager")
_jira_secret = None
def _get_jira_secret():
global _jira_secret
if _jira_secret is None:
response = _secrets_client.get_secret_value(SecretId="thinkwork/jira-credentials")
_jira_secret = json.loads(response["SecretString"])
return _jira_secret
Terminal window
# Get the workspace bucket name
BUCKET=$(thinkwork outputs -s dev --key bucket_name)
# Upload a complete skill folder for one tenant
aws s3 sync ./jira/ "s3://$BUCKET/tenants/acme/skill-catalog/jira/"

The skill is immediately available in that tenant’s admin Skills catalog — no Lambda redeployment needed. A user-facing zip upload is not required for the current catalog path; operators upload or edit the folder contents in the tenant catalog, then install from the Skills tab.

Open the admin Agent page, switch to Skills, and run Add Skill for the catalog folder. Installation copies the catalog folder into the target workspace as skills/<skill-slug>/ and writes .catalog-ref.json so stale/orphan status can be detected later.

Skill IDs correspond to the tenant catalog folder name: tenants/acme/skill-catalog/jira/ → skill ID jira.

Use the admin Agent Skills tab to browse tenant catalog folders and installed workspace folders. The catalog view maps directly to tenants/<tenant-slug>/skill-catalog/<skill-slug>/; the workspace view maps to installed skills/<skill-slug>/ folders.

When AgentCore receives a message for an agent with installed skill packs:

  1. Syncs the agent workspace from S3 into /tmp/workspace
  2. Discovers installed skill folders under skills/<slug>/
  3. Parses each SKILL.md
  4. Registers tools with the Strands agent loop
  5. Appends skill guidance through the AgentSkills progressive-disclosure plugin

The total latency depends on the number and size of installed workspace skill folders and the workspace sync cache state.

The examples/skill-pack/ directory in the ThinkWork repo contains ready-to-use skill packs:

SkillDescription
github.mdSearch issues, PRs, and code. Create issues and comments.
jira.mdSearch and create Jira issues. Transition issue status.
confluence.mdSearch Confluence pages. Create and update pages.
slack.mdPost messages and look up channel history.
sql.mdRun read-only SQL queries against the Aurora database.
calculator.mdMath and unit conversion tools.
datetime.mdDate parsing, formatting, and timezone conversion.
http.mdMake HTTP requests to external APIs.