Codebase Organization

The codebase has two trees: src/ (the runtime kernel) and skills/ (the tool library). A strict one-way dependency governs them: skills import from src, never the reverse. Tests mirror both trees under test/.

src/ — The Runtime Kernel

Eight concern-based subdirectories, plus root-level files:

Path Responsibility
src/types.ts Single source of truth for all types and interfaces. No type definitions elsewhere.
src/index.ts Public API re-exports.
src/cli.ts CLI entry point.
src/core/ Pure utilities with no framework knowledge: path expressions, serialization, topo-sort, ring-buffer, fuzzy matching, math, terminal detection, readline, state-store.
src/cascade/ The cascade algorithm: orchestrator, merge strategies, annotations, inheritance, string interpolation.
src/context/ Context creation, ContextManager, helpers (ctxTarget, ctxCwd, ctxPaths), manager-dispatch.
src/tools/ Tool resolution (lookup.ts), loading (loader.ts), registry (registry.ts), validation, allowed-tools parsing, module import.
src/log/ Logging system (log.ts, types.ts) and reporters/ subdirectory.
src/boot/ Bootstrap (the one builtin) and MCP server initialization.
src/cli/ CLI argument parsing.
src/testing/ Test helpers (mockContext, mockMiddlewareContext).

src/core/ Rule

src/core/ contains pure utilities that have no knowledge of the framework's domain concepts (contexts, tools, cascades). A module belongs in src/core/ only if it is used by two or more consumers. Single-consumer utilities are absorbed into their consumer — this was established during the codebase reorganization when scc.ts was absorbed into cascade/orchestrator.ts and glob.ts into tools/lookup.ts.

npm Dependencies in src/

The runtime kernel uses a small set of npm packages, each confined to specific modules:

Package Used in Purpose
gray-matter tools/loader.ts, tools/lookup.ts YAML frontmatter parsing
ajv tools/validate.ts JSON Schema validation
picomatch tools/lookup.ts Glob pattern matching
jsonpath-plus core/path.ts JSONPath expression evaluation
js-yaml log/reporters/format.ts YAML rendering for log output

Everything else in src/ uses only Node.js builtins.

File Size Aspirations

src/ files target ~200 lines. skills/ core tools target ~60 lines. skills/extensions/ tools have no specific target — they are larger by nature. These are aspirational, not rigid. Files can exceed them when the problem demands it.

skills/ — The Tool Library

Skills are organized into concern-based directories. The top-level directories under skills/ are not a fixed set — new tiers can be added as the framework grows. Each directory groups skills by their role in the system.

Core Tiers

Directory Role npm deps
skills/pipeline/ Standard pipeline middleware — execute, validate-args, directives, cwd, paths, params, model, etc. Includes default.skill.md (the library main skill). No
skills/directives/ Directive implementations — arg, env, path, skill, eval, inline, script, etc. Plus shared directive utilities (directive-utils.ts, directive-types.ts). No
skills/runtime/ Core runtime skills — events, callbacks, context-manager, invoke, cache-clear, and meta skills (skill-list, skill-describe, skill-register). No
skills/tools/ User-facing utility tools — files (read, write, list, glob, search), shell, code, help, web-fetch. No
skills/context/ Context skills (role: context) — prompt content injected into agent tool documentation. Not invoked directly. No

Core tiers do not use npm dependencies. They import only from src/ and from sibling files within their own directory.

Extensions

skills/extensions/ contains larger, more opinionated features that may use npm packages and have complex internal structure:

Subdirectory Feature Key npm deps
agent/ Agent policy, Strands/Bedrock provider, Kiro provider, proxy, sandbox @strands-agents/sdk, isolated-vm
web/ HTTP server and route dispatch path-to-regexp
websocket/ WebSocket server and socket dispatch
email/ SES/S3 email polling and dispatch
slack/ Socket Mode Slack integration
webhook/ Standard Webhooks receiver
gateway/ Channel send/receive dispatch (CLI, Slack, email, web)
hub/ Package registry (search, add, remove, update, publish)
toolchain/ Compiler suite (compile, decompile, test, analyze, revise, architect, etc.)
trust/ Tool execution gating with approval prompts
roles/ Identity-based metadata injection
session/ Conversational memory across invocations
schedule/ Timer-based skill scheduling with cron, file persistence croner
state/ Key-value persistent state
diagnostics/ Debug, profile, observe hooks
mcp/ MCP server (expose skills as MCP tools) zod, @modelcontextprotocol/sdk
infra/ Deploy (App Runner) and sandbox (Docker)
auth/ Auth providers (token, OAuth, internal) jose
amazon/ Amazon-internal auth and deploy providers jose
repl/ Interactive agent session
tutor/ Interactive learning lessons
init/ Project scaffolding
update/ CLI update
event/ Event metadata middleware

Each extension subsystem owns its own helper files (named *-utils.ts, *-types.ts, *-state.ts, *-lib.ts, *-store.ts). These are private to their directory.

Import Hierarchy

The dependency graph is strictly layered:

src/core/                          Pure utilities, no framework knowledge
    ↑
src/{cascade,context,tools,       Runtime subsystems (import freely from
     log,boot,cli,testing}         each other and from core/)
    ↑
skills/{pipeline,directives,      Core skills (import from src/ only,
        runtime,tools,context}     plus sibling files in own directory)
    ↑
skills/extensions/                 Extensions (import from src/ and
                                   downward into core skill tiers)

Arrows mean "may import from." No imports flow in the reverse direction. src/ never imports from skills/. Core skills never import from extensions.

What Skills Import from src/

The most common imports from src/ into skills, by frequency:

Module Purpose Typical importers
src/types.ts Context, JsonObject, ToolDef, etc. Every skill
src/context/helpers.ts ctxTarget, ctxCwd, ctxPaths, toText All middleware
src/tools/lookup.ts findTool, listTools, registerTool, createCallbackRef, matchPattern Runtime skills, extensions
src/core/serialize.ts stringify, parse Skills that format output

Other src/ modules are imported sparingly and only by skills that genuinely need them.

Cross-Tier Imports Within skills/

Most skills only import from src/ and from sibling files in their own directory. The following cross-tier imports exist within the library and are considered sanctioned:

From To What Why
skills/pipeline/ skills/directives/ extractDirectives, stripHtmlComments, toolDir Pipeline orchestrates directive processing
skills/extensions/* skills/extensions/gateway/gateway-utils.ts setGateway Shared gateway interface for all transport providers
skills/extensions/* skills/runtime/events/event-types.ts Event type Type-only import for event handling
skills/extensions/event/ skills/runtime/events/event-state.ts getEvents Event middleware checks subscription state
skills/extensions/session/ skills/directives/ DirectiveArgs type, escapeRegex Session-history is a directive
skills/extensions/toolchain/ skills/directives/ extractDirectives Toolchain analyzes directive usage in skills

All cross-tier imports flow downward (extensions → core tiers) or laterally within the same concern. No core skill imports from extensions.

Rule for External Skills

Hub packages and user-authored skills should import only from src/ (via the agent-apps package entry point) and from their own files. Cross-skill imports are an internal library convenience — external skills must not depend on the library's internal file structure.

Utility File Convention

Non-.skill.ts TypeScript files within skills/ are private helpers scoped to their containing directory. There is no shared utility directory in skills/. This was established when skills/common/ (a 12-file grab bag of shared utilities) was eliminated during the codebase reorganization — its contents were distributed to src/ (for pure utilities) or to their owning skill subsystem (for domain-specific helpers).

Naming conventions for helper files:

Suffix Purpose Examples
*-utils.ts Shared utility functions directive-utils.ts, gateway-utils.ts, email-util.ts
*-types.ts Type definitions directive-types.ts, agent-types.ts, infra-types.ts
*-state.ts Globals-backed state accessors event-state.ts, callback-state.ts, slack-state.ts
*-lib.ts Substantial shared logic hub-lib.ts, trust-lib.ts, state-lib.ts
*-store.ts Persistence helpers trust-store-lib.ts, schedule-store.ts, token-store.ts

Test Organization

Tests mirror the source tree:

Source Test
src/cascade/merge.ts test/src/cascade/merge.test.ts
skills/pipeline/cwd.skill.ts test/skills/pipeline/cwd.test.ts
skills/extensions/agent/agent.skill.ts test/skills/extensions/agent/agent.test.ts

Integration tests (test/integration/) are organized by subsystem. Acceptance tests (test/acceptance/) are organized by feature. See Testing Strategy for the full test methodology.

Enforcement

  • madge runs on every commit via lefthook, checking for circular dependencies across src/ and skills/ combined. Only src/types.ts and src/cascade/inherit.ts are excluded (they have legitimate circular references with their consumers).
  • Convention — consistent patterns across hundreds of files, enforced by code review.
  • npm run check — TypeScript strict mode, Biome linting, knip (unused exports), eslint (type-aware rules), and madge all run together.

Skill Design Conventions

These conventions govern how skills expose configuration, accept arguments, and interact with each other. They ensure consistency across the library so skills feel cohesive rather than ad-hoc.

One Way to Write Things

Every configuration shape has exactly one canonical form. No shorthand aliases, no "if this key is missing, treat the whole object as a default." If a skill accepts a list, it always takes a list — even for the single-item case. This eliminates ambiguity in documentation, tests, and user mental models.

Wrong: Two ways to write the same config.

# "Shorthand" — bare object treated as default
email:
  from: bot@example.com
  bucket: ses-bucket
# "Explicit" — wrapped in profiles
email:
  profiles:
    default:
      from: bot@example.com
      bucket: ses-bucket

Right: One canonical form.

email:
  from: bot@example.com
  bucket: ses-bucket
  routes:
    - ref: handler-skill

Lists Over Keyed Maps for Iteration

When a skill iterates over a collection (routes, providers, rules), use an array of objects — not a keyed map. Keyed maps are for lookup by name (e.g., deploy-app --arg name=prod). If the key is only used for logging, it doesn't justify a map.

Wrong: Keyed map where the key is cosmetic.

slack:
  profiles:
    my-bot:
      app-token: xapp-...
      routes:
        - ref: handler

Right: Flat config with a routes list.

slack:
  app-token: xapp-...
  routes:
    - ref: handler

Keyed maps are appropriate when the key is a lookup target:

deploy:
  profiles:
    prod:
      provider: apprunner
      region: us-east-1

Consistent Key Names

Skills that dispatch inbound messages to other skills use the same vocabulary:

Key Meaning Used by
routes List of dispatch rules, each with a ref to the target skill web, slack, email, webhook, websocket
ref Skill name to invoke when a route matches All route entries
verify List of authentication providers tried in order auth
rules List of pattern-matching rules roles

Filter properties on route entries are transport-specific (from, subject, method, path, socket, etc.) but ref is universal.

Optional name Field

Every list entry (routes, verify, rules) supports an optional name property. It serves two purposes:

  1. Logging and correlation. The name appears in log events and gateway identity.
  2. Targeted lookup. Consumer skills that need to reference a specific entry search by name (e.g., email-setup --name inbox, auth-token-create --name api).

When name is omitted, a sensible default is derived from the entry's required fields:

List Default name
slack routes ref value
email routes ref value
webhook routes path value
websocket routes socket value (required)
auth verify provider value
roles rules match pattern

Do not require name when a sensible default exists. Users only set it when they need to target a specific entry from another skill.

Gateway Integration

Transport middleware (web, slack, email, websocket, webhook, MCP) sets up a gateway for each inbound connection using setGateway() from gateway-utils.ts. The gateway's send and receive callbacks are real functions that call the provider skills with correlation args baked in. The callback ref params reflect the transport-specific args, not a generic union.

Skills that need to communicate with the user call gateway-send and gateway-receive — they never call provider skills directly unless reaching a different user or transport.

Middleware Metadata Params

Middleware skills declare params: { type: "object" } as a minimum. The params declaration documents what the middleware accepts as cascade metadata — it is informational, not enforced by validation. Middleware params should enumerate their properties so agents and MCP callers can discover the configuration shape.

Skill Signatures

All skills use typed signatures:

Pattern When
(ctx: Context) No args expected
(ctx: Context, args: DirectiveArgs) Directive skills
(ctx: Context, args: unknown) Skills that narrow args internally
(ctx: Context, args: { specific: types }) Skills with known arg shapes

Middleware skills always call ctxTarget(ctx) to get the served context. They never operate on ctx directly (which is the middleware's own context).

Frontmatter Conventions

  • name: lowercase-with-hyphens, max 64 characters, no double hyphens
  • description: one sentence, present tense, describes what the skill does
  • metadata.role: main, middleware, or context
  • metadata.visibility: hidden for internal plumbing skills
  • metadata.tags: lowercase-with-hyphens, meta-internal for framework internals, meta-metadata for middleware that processes cascade keys
  • metadata.$private: ["$self"]: prevents the skill from appearing as a toolKey in its own cascade (required for middleware that appears in default.skill.md) and keeps per-skill middleware private automatically (transport middleware, session, state, auth, roles, schedule, event, delegate)
  • metadata.$global: ["$self"]: auto-promotes the skill's cascade value to all tools when set on the main skill
  • allowed-tools: top-level frontmatter field (not under metadata), space-delimited glob patterns

Ask AI