@poncho-ai/harness 0.47.1 → 0.48.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
 
2
- > @poncho-ai/harness@0.47.1 build /home/runner/work/poncho-ai/poncho-ai/packages/harness
2
+ > @poncho-ai/harness@0.48.0 build /home/runner/work/poncho-ai/poncho-ai/packages/harness
3
3
  > node scripts/embed-docs.js && tsup src/index.ts --format esm --dts
4
4
 
5
5
  [embed-docs] Generated poncho-docs.ts with 4 topics
@@ -8,9 +8,9 @@
8
8
  CLI tsup v8.5.1
9
9
  CLI Target: es2022
10
10
  ESM Build start
11
- ESM dist/index.js 527.60 KB
11
+ ESM dist/index.js 528.41 KB
12
12
  ESM dist/isolate-VY35DGLM.js 49.43 KB
13
- ESM ⚡️ Build success in 275ms
13
+ ESM ⚡️ Build success in 216ms
14
14
  DTS Build start
15
- DTS ⚡️ Build success in 7513ms
16
- DTS dist/index.d.ts 86.25 KB
15
+ DTS ⚡️ Build success in 7309ms
16
+ DTS dist/index.d.ts 87.43 KB
package/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # @poncho-ai/harness
2
2
 
3
+ ## 0.48.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#125](https://github.com/cesr/poncho-ai/pull/125) [`ff66aae`](https://github.com/cesr/poncho-ai/commit/ff66aaeebe6017ca9e1ee4b31ffe0d89bdf5ef28) Thanks [@cesr](https://github.com/cesr)! - harness: add `systemSkillPaths` for platform-shipped system skills
8
+
9
+ New optional `HarnessOptions.systemSkillPaths` (absolute directories,
10
+ each scanned for `<name>/SKILL.md` at init). System skills are surfaced
11
+ in `<available_skills>` like any other skill, with their bodies read
12
+ from local disk on activation — letting a platform ship default skills
13
+ with the deploy instead of writing them into every tenant's VFS.
14
+
15
+ Precedence is purely additive: per tenant the skill set resolves as
16
+ repo skills > the tenant's own VFS skills > system skills. So a tenant's
17
+ `/skills/<same-name>/` overrides a same-named system skill (mirroring
18
+ the VFS override behavior platforms already rely on for system jobs),
19
+ and the existing repo-vs-VFS precedence is unchanged. Empty by default —
20
+ no behavior change for existing consumers.
21
+
22
+ Also exports `loadSkillMetadataFromDirs(dirs)` (extracted from
23
+ `loadSkillMetadata`) for scanning an explicit list of absolute skill
24
+ directories.
25
+
3
26
  ## 0.47.1
4
27
 
5
28
  ### Patch Changes
package/dist/index.d.ts CHANGED
@@ -1211,6 +1211,17 @@ interface HarnessOptions {
1211
1211
  * Empty by default — no system mounts in the CLI / dev workflow.
1212
1212
  */
1213
1213
  virtualMounts?: VirtualMount[];
1214
+ /**
1215
+ * Absolute directories of platform-shipped "system" skills. Each is
1216
+ * scanned for `<name>/SKILL.md` at init; the bodies live on local disk
1217
+ * and ship with the deploy. System skills are surfaced in
1218
+ * `<available_skills>` like any other skill, but sit at the LOWEST
1219
+ * precedence: a tenant's own `/skills/<same-name>/` (and a repo skill)
1220
+ * overrides a system skill of the same name. Pair with a read-only
1221
+ * `virtualMounts` entry (e.g. "/system/skills/") if the same files
1222
+ * should also be browsable in the VFS. Empty by default.
1223
+ */
1224
+ systemSkillPaths?: string[];
1214
1225
  }
1215
1226
  interface HarnessRunOutput {
1216
1227
  runId: string;
@@ -1243,6 +1254,8 @@ declare class AgentHarness {
1243
1254
  private loadedConfig?;
1244
1255
  private readonly injectedConfig?;
1245
1256
  private loadedSkills;
1257
+ private systemSkills;
1258
+ private readonly systemSkillPaths;
1246
1259
  private skillFingerprint;
1247
1260
  private lastSkillRefreshAt;
1248
1261
  private readonly activeSkillNames;
@@ -1298,10 +1311,15 @@ declare class AgentHarness {
1298
1311
  private getMemoryStore;
1299
1312
  private listActiveSkills;
1300
1313
  /**
1301
- * Resolve the skill set visible to a given tenant: repo skills plus that
1302
- * tenant's VFS skills, with repo winning on name collision. Cached per
1303
- * tenant; cache invalidates on VFS writes under /skills/ via
1304
- * invalidateSkillsForTenant.
1314
+ * Resolve the skill set visible to a given tenant. Three tiers, by
1315
+ * precedence: repo skills > the tenant's own VFS skills > platform
1316
+ * system skills. So a repo skill wins over a same-named VFS skill
1317
+ * (unchanged), and a tenant's `/skills/<name>/` overrides a same-named
1318
+ * system skill (the deploy-shipped default). Cached per tenant; cache
1319
+ * invalidates on VFS writes under /skills/ via invalidateSkillsForTenant.
1320
+ * System skills are static within a process, so they don't participate
1321
+ * in the fingerprint — but a VFS override does (it changes a /skills
1322
+ * path), which recomputes the cache and lets the override take effect.
1305
1323
  */
1306
1324
  private getSkillsForTenant;
1307
1325
  invalidateSkillsForTenant(tenantId: string): void;
@@ -1451,6 +1469,7 @@ declare const parseSkillFrontmatter: (content: string) => {
1451
1469
  };
1452
1470
  } | undefined;
1453
1471
  declare const loadSkillMetadata: (workingDir: string, extraSkillPaths?: string[]) => Promise<SkillMetadata[]>;
1472
+ declare const loadSkillMetadataFromDirs: (skillDirs: string[]) => Promise<SkillMetadata[]>;
1454
1473
  declare const buildSkillContextWindow: (skills: SkillMetadata[]) => string;
1455
1474
  declare const loadVfsSkillMetadata: (engine: StorageEngine, tenantId: string) => Promise<SkillMetadata[]>;
1456
1475
  declare const mergeSkills: (repoSkills: SkillMetadata[], vfsSkills: SkillMetadata[], onCollision?: (vfsSkill: SkillMetadata) => void) => SkillMetadata[];
@@ -2080,4 +2099,4 @@ interface RunConversationTurnResult {
2080
2099
  }
2081
2100
  declare const runConversationTurn: (opts: RunConversationTurnOpts) => Promise<RunConversationTurnResult>;
2082
2101
 
2083
- export { type ActiveConversationRun, type ActiveSubagentRun, type AgentFrontmatter, AgentHarness, type AgentIdentity, type AgentLimitsConfig, type AgentModelConfig, AgentOrchestrator, type ApprovalEventItem, type ArchivedToolResult$1 as ArchivedToolResult, type BashConfig, BashEnvironmentManager, type BashExecutionLimits, type BuiltInToolToggles, CALLBACK_LOCK_STALE_MS, type CompactMessagesOptions, type CompactResult, type CompactionConfig, type ContinuationHooks, type Conversation, type ConversationCreateInit, type ConversationState, type ConversationStatusSnapshot, type ConversationStore, type ConversationSummary, type CreateSkillToolsOptions, type CronJobConfig, DEFAULT_AGENT_DESCRIPTION, DEFAULT_AGENT_NAME, DEFAULT_MAX_STEPS, DEFAULT_MODEL_NAME, DEFAULT_MODEL_PROVIDER, DEFAULT_TEMPERATURE, DEFAULT_TIMEOUT, type DefaultAgentDefinitionOptions, type EventSink, type ExecuteTurnResult, type HarnessOptions, type HarnessRunOutput, type HistorySource, InMemoryConversationStore, InMemoryEngine, InMemoryStateStore, type IsolateBinding, type IsolateConfig, LocalMcpBridge, LocalUploadStore, MAX_CONCURRENT_SUBAGENTS, MAX_CONTINUATION_COUNT, MAX_SUBAGENT_CALLBACK_COUNT, MAX_SUBAGENT_NESTING, type MainMemory, type McpConfig, type MemoryConfig, type MemoryStore, type MessagingChannelConfig, type ModelProviderFactory, type NetworkConfig, OPENAI_CODEX_CLIENT_ID, type OpenAICodexAuthConfig, type OpenAICodexDeviceAuthRequest, type OpenAICodexSession, type OrchestratorHooks, type OrchestratorOptions, type OtlpConfig, type OtlpOption, PONCHO_UPLOAD_SCHEME, type ParsedAgent, type PendingSubagentApproval, type PendingSubagentResult, type PendingToolCall, type PonchoConfig, PonchoFsAdapter, PostgresEngine, type ProviderConfig, type Recurrence, type RecurrenceType, type Reminder, type ReminderCreateInput, type ReminderStatus, type ReminderStore, type RemoteMcpServerConfig, type RunConversationTurnOpts, type RunConversationTurnResult, type RunOutcome, type RunRequest, type RuntimeRenderContext, S3UploadStore, STALE_SUBAGENT_THRESHOLD_MS, STORAGE_SCHEMA_VERSION, type SecretsStore, type SkillContextEntry, type SkillMetadata, type SkillSource, SqliteEngine, type StateConfig, type StateProviderName, type StateStore, type StorageConfig, type StorageEngine, type StorageFactoryOptions, type StorageProvider, type StoredApproval, type SubagentManager, type SubagentResult, type SubagentSpawnResult, type SubagentSummary, type SubagentTranscript, type SubagentTranscriptMode, TOOL_RESULT_ARCHIVE_PARAM, type TelemetryConfig, TelemetryEmitter, type TenantTokenPayload, type ToolAccess, type ToolCall, ToolDispatcher, type ToolExecutionResult, type TurnDraftState, type TurnResultMetadata, type TurnSection, type UploadStore, type UploadsConfig, VFS_SCHEME, VercelBlobUploadStore, type VfsDirEntry, type VfsStat, type VirtualMount, applyTurnMetadata, buildAgentDirectoryName, buildApprovalCheckpoints, buildAssistantMetadata, buildSkillContextWindow, buildToolCompletedText, cloneSections, compactMessages, completeOpenAICodexDeviceAuth, computeNextOccurrence, createBashTool, createConversationStore, createConversationStoreFromEngine, createDefaultTools, createDeleteDirectoryTool, createDeleteTool, createEditTool, createMemoryStore, createMemoryStoreFromEngine, createMemoryTools, createModelProvider, createReminderStore, createReminderStoreFromEngine, createReminderTools, createSearchTools, createSecretsStore, createSkillTools, createStateStore, createStorageEngine, createSubagentTools, createTodoStoreFromEngine, createTurnDraftState, createUploadStore, createWriteTool, decodeFileInputData, defaultAgentDefinition, deleteOpenAICodexSession, deriveUploadKey, ensureAgentIdentity, estimateTokens, estimateTotalTokens, executeConversationTurn, findSafeSplitPoint, flushTurnDraft, generateAgentId, getAgentStoreDirectory, getModelContextWindow, getOpenAICodexAccessToken, getOpenAICodexAuthFilePath, getOpenAICodexRequiredScopes, getPonchoStoreRoot, isMessageArray, jsonSchemaToZod, loadCanonicalHistory, loadPonchoConfig, loadRunHistory, loadSkillContext, loadSkillInstructions, loadSkillMetadata, loadVfsSkillMetadata, mergeSkills, normalizeApprovalCheckpoint, normalizeOtlp, normalizeScriptPolicyPath, normalizeToolAccess, parseAgentFile, parseAgentMarkdown, parseSkillFrontmatter, ponchoDocsTool, readOpenAICodexSession, readSkillResource, recordStandardTurnEvent, renderAgentPrompt, resolveAgentIdentity, resolveCompactionConfig, resolveEnv, resolveMemoryConfig, resolveRunRequest, resolveSkillDirs, resolveStateConfig, runConversationTurn, slugifyStorageComponent, startOpenAICodexDeviceAuth, verifyTenantToken, withToolResultArchiveParam, writeOpenAICodexSession };
2102
+ export { type ActiveConversationRun, type ActiveSubagentRun, type AgentFrontmatter, AgentHarness, type AgentIdentity, type AgentLimitsConfig, type AgentModelConfig, AgentOrchestrator, type ApprovalEventItem, type ArchivedToolResult$1 as ArchivedToolResult, type BashConfig, BashEnvironmentManager, type BashExecutionLimits, type BuiltInToolToggles, CALLBACK_LOCK_STALE_MS, type CompactMessagesOptions, type CompactResult, type CompactionConfig, type ContinuationHooks, type Conversation, type ConversationCreateInit, type ConversationState, type ConversationStatusSnapshot, type ConversationStore, type ConversationSummary, type CreateSkillToolsOptions, type CronJobConfig, DEFAULT_AGENT_DESCRIPTION, DEFAULT_AGENT_NAME, DEFAULT_MAX_STEPS, DEFAULT_MODEL_NAME, DEFAULT_MODEL_PROVIDER, DEFAULT_TEMPERATURE, DEFAULT_TIMEOUT, type DefaultAgentDefinitionOptions, type EventSink, type ExecuteTurnResult, type HarnessOptions, type HarnessRunOutput, type HistorySource, InMemoryConversationStore, InMemoryEngine, InMemoryStateStore, type IsolateBinding, type IsolateConfig, LocalMcpBridge, LocalUploadStore, MAX_CONCURRENT_SUBAGENTS, MAX_CONTINUATION_COUNT, MAX_SUBAGENT_CALLBACK_COUNT, MAX_SUBAGENT_NESTING, type MainMemory, type McpConfig, type MemoryConfig, type MemoryStore, type MessagingChannelConfig, type ModelProviderFactory, type NetworkConfig, OPENAI_CODEX_CLIENT_ID, type OpenAICodexAuthConfig, type OpenAICodexDeviceAuthRequest, type OpenAICodexSession, type OrchestratorHooks, type OrchestratorOptions, type OtlpConfig, type OtlpOption, PONCHO_UPLOAD_SCHEME, type ParsedAgent, type PendingSubagentApproval, type PendingSubagentResult, type PendingToolCall, type PonchoConfig, PonchoFsAdapter, PostgresEngine, type ProviderConfig, type Recurrence, type RecurrenceType, type Reminder, type ReminderCreateInput, type ReminderStatus, type ReminderStore, type RemoteMcpServerConfig, type RunConversationTurnOpts, type RunConversationTurnResult, type RunOutcome, type RunRequest, type RuntimeRenderContext, S3UploadStore, STALE_SUBAGENT_THRESHOLD_MS, STORAGE_SCHEMA_VERSION, type SecretsStore, type SkillContextEntry, type SkillMetadata, type SkillSource, SqliteEngine, type StateConfig, type StateProviderName, type StateStore, type StorageConfig, type StorageEngine, type StorageFactoryOptions, type StorageProvider, type StoredApproval, type SubagentManager, type SubagentResult, type SubagentSpawnResult, type SubagentSummary, type SubagentTranscript, type SubagentTranscriptMode, TOOL_RESULT_ARCHIVE_PARAM, type TelemetryConfig, TelemetryEmitter, type TenantTokenPayload, type ToolAccess, type ToolCall, ToolDispatcher, type ToolExecutionResult, type TurnDraftState, type TurnResultMetadata, type TurnSection, type UploadStore, type UploadsConfig, VFS_SCHEME, VercelBlobUploadStore, type VfsDirEntry, type VfsStat, type VirtualMount, applyTurnMetadata, buildAgentDirectoryName, buildApprovalCheckpoints, buildAssistantMetadata, buildSkillContextWindow, buildToolCompletedText, cloneSections, compactMessages, completeOpenAICodexDeviceAuth, computeNextOccurrence, createBashTool, createConversationStore, createConversationStoreFromEngine, createDefaultTools, createDeleteDirectoryTool, createDeleteTool, createEditTool, createMemoryStore, createMemoryStoreFromEngine, createMemoryTools, createModelProvider, createReminderStore, createReminderStoreFromEngine, createReminderTools, createSearchTools, createSecretsStore, createSkillTools, createStateStore, createStorageEngine, createSubagentTools, createTodoStoreFromEngine, createTurnDraftState, createUploadStore, createWriteTool, decodeFileInputData, defaultAgentDefinition, deleteOpenAICodexSession, deriveUploadKey, ensureAgentIdentity, estimateTokens, estimateTotalTokens, executeConversationTurn, findSafeSplitPoint, flushTurnDraft, generateAgentId, getAgentStoreDirectory, getModelContextWindow, getOpenAICodexAccessToken, getOpenAICodexAuthFilePath, getOpenAICodexRequiredScopes, getPonchoStoreRoot, isMessageArray, jsonSchemaToZod, loadCanonicalHistory, loadPonchoConfig, loadRunHistory, loadSkillContext, loadSkillInstructions, loadSkillMetadata, loadSkillMetadataFromDirs, loadVfsSkillMetadata, mergeSkills, normalizeApprovalCheckpoint, normalizeOtlp, normalizeScriptPolicyPath, normalizeToolAccess, parseAgentFile, parseAgentMarkdown, parseSkillFrontmatter, ponchoDocsTool, readOpenAICodexSession, readSkillResource, recordStandardTurnEvent, renderAgentPrompt, resolveAgentIdentity, resolveCompactionConfig, resolveEnv, resolveMemoryConfig, resolveRunRequest, resolveSkillDirs, resolveStateConfig, runConversationTurn, slugifyStorageComponent, startOpenAICodexDeviceAuth, verifyTenantToken, withToolResultArchiveParam, writeOpenAICodexSession };
package/dist/index.js CHANGED
@@ -7363,7 +7363,9 @@ var collectSkillManifests = async (directory) => {
7363
7363
  return files;
7364
7364
  };
7365
7365
  var loadSkillMetadata = async (workingDir, extraSkillPaths) => {
7366
- const skillDirs = resolveSkillDirs(workingDir, extraSkillPaths);
7366
+ return loadSkillMetadataFromDirs(resolveSkillDirs(workingDir, extraSkillPaths));
7367
+ };
7368
+ var loadSkillMetadataFromDirs = async (skillDirs) => {
7367
7369
  const allManifests = [];
7368
7370
  for (const dir of skillDirs) {
7369
7371
  try {
@@ -9120,6 +9122,8 @@ var AgentHarness = class _AgentHarness {
9120
9122
  loadedConfig;
9121
9123
  injectedConfig;
9122
9124
  loadedSkills = [];
9125
+ systemSkills = [];
9126
+ systemSkillPaths = [];
9123
9127
  skillFingerprint = "";
9124
9128
  lastSkillRefreshAt = 0;
9125
9129
  activeSkillNames = /* @__PURE__ */ new Set();
@@ -9325,6 +9329,7 @@ var AgentHarness = class _AgentHarness {
9325
9329
  this.injectedStorageEngine = true;
9326
9330
  }
9327
9331
  this.virtualMounts = options.virtualMounts ?? [];
9332
+ this.systemSkillPaths = options.systemSkillPaths ?? [];
9328
9333
  if (options.toolDefinitions?.length) {
9329
9334
  this.dispatcher.registerMany(options.toolDefinitions);
9330
9335
  }
@@ -9486,14 +9491,19 @@ var AgentHarness = class _AgentHarness {
9486
9491
  return [...this.activeSkillNames].sort();
9487
9492
  }
9488
9493
  /**
9489
- * Resolve the skill set visible to a given tenant: repo skills plus that
9490
- * tenant's VFS skills, with repo winning on name collision. Cached per
9491
- * tenant; cache invalidates on VFS writes under /skills/ via
9492
- * invalidateSkillsForTenant.
9494
+ * Resolve the skill set visible to a given tenant. Three tiers, by
9495
+ * precedence: repo skills > the tenant's own VFS skills > platform
9496
+ * system skills. So a repo skill wins over a same-named VFS skill
9497
+ * (unchanged), and a tenant's `/skills/<name>/` overrides a same-named
9498
+ * system skill (the deploy-shipped default). Cached per tenant; cache
9499
+ * invalidates on VFS writes under /skills/ via invalidateSkillsForTenant.
9500
+ * System skills are static within a process, so they don't participate
9501
+ * in the fingerprint — but a VFS override does (it changes a /skills
9502
+ * path), which recomputes the cache and lets the override take effect.
9493
9503
  */
9494
9504
  async getSkillsForTenant(tenantId) {
9495
9505
  if (!this.storageEngine) {
9496
- return this.loadedSkills;
9506
+ return mergeSkills(this.loadedSkills, this.systemSkills);
9497
9507
  }
9498
9508
  const effectiveTenant = tenantId || "__default__";
9499
9509
  const engineWithRefresh = this.storageEngine;
@@ -9506,7 +9516,7 @@ var AgentHarness = class _AgentHarness {
9506
9516
  return cached.skills;
9507
9517
  }
9508
9518
  const vfsSkills = await loadVfsSkillMetadata(this.storageEngine, effectiveTenant);
9509
- const merged = mergeSkills(this.loadedSkills, vfsSkills, (skipped) => {
9519
+ const repoAndVfs = mergeSkills(this.loadedSkills, vfsSkills, (skipped) => {
9510
9520
  const key = `${effectiveTenant}:${skipped.name}`;
9511
9521
  if (this.vfsSkillCollisionWarnings.has(key)) return;
9512
9522
  this.vfsSkillCollisionWarnings.add(key);
@@ -9514,6 +9524,7 @@ var AgentHarness = class _AgentHarness {
9514
9524
  `VFS skill "${skipped.name}" for tenant ${effectiveTenant} ignored: a repo skill with the same name takes precedence.`
9515
9525
  );
9516
9526
  });
9527
+ const merged = mergeSkills(repoAndVfs, this.systemSkills);
9517
9528
  this.skillCache.set(effectiveTenant, { skills: merged, fingerprint });
9518
9529
  return merged;
9519
9530
  }
@@ -9844,6 +9855,7 @@ var AgentHarness = class _AgentHarness {
9844
9855
  const extraSkillPaths = config?.skillPaths;
9845
9856
  const skillMetadata = await loadSkillMetadata(this.workingDir, extraSkillPaths);
9846
9857
  this.loadedSkills = skillMetadata;
9858
+ this.systemSkills = this.systemSkillPaths.length ? await loadSkillMetadataFromDirs(this.systemSkillPaths) : [];
9847
9859
  this.skillFingerprint = this.buildSkillFingerprint(skillMetadata);
9848
9860
  this.registerSkillTools();
9849
9861
  const agentId = this.parsedAgent.frontmatter.id ?? this.parsedAgent.frontmatter.name;
@@ -13980,6 +13992,7 @@ export {
13980
13992
  loadSkillContext,
13981
13993
  loadSkillInstructions,
13982
13994
  loadSkillMetadata,
13995
+ loadSkillMetadataFromDirs,
13983
13996
  loadVfsSkillMetadata,
13984
13997
  mergeSkills,
13985
13998
  normalizeApprovalCheckpoint,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poncho-ai/harness",
3
- "version": "0.47.1",
3
+ "version": "0.48.0",
4
4
  "description": "Agent execution runtime - conversation loop, tool dispatch, streaming",
5
5
  "repository": {
6
6
  "type": "git",
package/src/harness.ts CHANGED
@@ -55,6 +55,7 @@ import { createModelProvider, getModelContextWindow, type ModelProviderFactory,
55
55
  import {
56
56
  buildSkillContextWindow,
57
57
  loadSkillMetadata,
58
+ loadSkillMetadataFromDirs,
58
59
  loadVfsSkillMetadata,
59
60
  mergeSkills,
60
61
  } from "./skill-context.js";
@@ -134,6 +135,17 @@ export interface HarnessOptions {
134
135
  * Empty by default — no system mounts in the CLI / dev workflow.
135
136
  */
136
137
  virtualMounts?: VirtualMount[];
138
+ /**
139
+ * Absolute directories of platform-shipped "system" skills. Each is
140
+ * scanned for `<name>/SKILL.md` at init; the bodies live on local disk
141
+ * and ship with the deploy. System skills are surfaced in
142
+ * `<available_skills>` like any other skill, but sit at the LOWEST
143
+ * precedence: a tenant's own `/skills/<same-name>/` (and a repo skill)
144
+ * overrides a system skill of the same name. Pair with a read-only
145
+ * `virtualMounts` entry (e.g. "/system/skills/") if the same files
146
+ * should also be browsable in the VFS. Empty by default.
147
+ */
148
+ systemSkillPaths?: string[];
137
149
  }
138
150
 
139
151
  export interface HarnessRunOutput {
@@ -839,6 +851,8 @@ export class AgentHarness {
839
851
  private loadedConfig?: PonchoConfig;
840
852
  private readonly injectedConfig?: PonchoConfig;
841
853
  private loadedSkills: SkillMetadata[] = [];
854
+ private systemSkills: SkillMetadata[] = [];
855
+ private readonly systemSkillPaths: string[] = [];
842
856
  private skillFingerprint = "";
843
857
  private lastSkillRefreshAt = 0;
844
858
  private readonly activeSkillNames = new Set<string>();
@@ -1077,6 +1091,7 @@ export class AgentHarness {
1077
1091
  this.injectedStorageEngine = true;
1078
1092
  }
1079
1093
  this.virtualMounts = options.virtualMounts ?? [];
1094
+ this.systemSkillPaths = options.systemSkillPaths ?? [];
1080
1095
 
1081
1096
  if (options.toolDefinitions?.length) {
1082
1097
  this.dispatcher.registerMany(options.toolDefinitions);
@@ -1271,14 +1286,19 @@ export class AgentHarness {
1271
1286
  }
1272
1287
 
1273
1288
  /**
1274
- * Resolve the skill set visible to a given tenant: repo skills plus that
1275
- * tenant's VFS skills, with repo winning on name collision. Cached per
1276
- * tenant; cache invalidates on VFS writes under /skills/ via
1277
- * invalidateSkillsForTenant.
1289
+ * Resolve the skill set visible to a given tenant. Three tiers, by
1290
+ * precedence: repo skills > the tenant's own VFS skills > platform
1291
+ * system skills. So a repo skill wins over a same-named VFS skill
1292
+ * (unchanged), and a tenant's `/skills/<name>/` overrides a same-named
1293
+ * system skill (the deploy-shipped default). Cached per tenant; cache
1294
+ * invalidates on VFS writes under /skills/ via invalidateSkillsForTenant.
1295
+ * System skills are static within a process, so they don't participate
1296
+ * in the fingerprint — but a VFS override does (it changes a /skills
1297
+ * path), which recomputes the cache and lets the override take effect.
1278
1298
  */
1279
1299
  private async getSkillsForTenant(tenantId: string | undefined | null): Promise<SkillMetadata[]> {
1280
1300
  if (!this.storageEngine) {
1281
- return this.loadedSkills;
1301
+ return mergeSkills(this.loadedSkills, this.systemSkills);
1282
1302
  }
1283
1303
  // Mirror the rest of the harness: undefined tenantId falls back to
1284
1304
  // "__default__" so dev-mode (no auth) conversations see the same VFS
@@ -1305,7 +1325,7 @@ export class AgentHarness {
1305
1325
  return cached.skills;
1306
1326
  }
1307
1327
  const vfsSkills = await loadVfsSkillMetadata(this.storageEngine, effectiveTenant);
1308
- const merged = mergeSkills(this.loadedSkills, vfsSkills, (skipped) => {
1328
+ const repoAndVfs = mergeSkills(this.loadedSkills, vfsSkills, (skipped) => {
1309
1329
  const key = `${effectiveTenant}:${skipped.name}`;
1310
1330
  if (this.vfsSkillCollisionWarnings.has(key)) return;
1311
1331
  this.vfsSkillCollisionWarnings.add(key);
@@ -1313,6 +1333,11 @@ export class AgentHarness {
1313
1333
  `VFS skill "${skipped.name}" for tenant ${effectiveTenant} ignored: a repo skill with the same name takes precedence.`,
1314
1334
  );
1315
1335
  });
1336
+ // System skills sit at the bottom: a repo or VFS skill of the same
1337
+ // name overrides them. Overriding a system default is the intended
1338
+ // user workflow (mirrors /jobs system-default overrides), so the
1339
+ // collision is silent — not a warning.
1340
+ const merged = mergeSkills(repoAndVfs, this.systemSkills);
1316
1341
  this.skillCache.set(effectiveTenant, { skills: merged, fingerprint });
1317
1342
  return merged;
1318
1343
  }
@@ -1706,6 +1731,13 @@ export class AgentHarness {
1706
1731
  const extraSkillPaths = config?.skillPaths;
1707
1732
  const skillMetadata = await loadSkillMetadata(this.workingDir, extraSkillPaths);
1708
1733
  this.loadedSkills = skillMetadata;
1734
+ // Platform-shipped system skills, scanned from absolute dirs on disk.
1735
+ // Loaded once at init (they ship with the deploy and don't change
1736
+ // within a process). Merged at LOWEST precedence per tenant — see
1737
+ // getSkillsForTenant.
1738
+ this.systemSkills = this.systemSkillPaths.length
1739
+ ? await loadSkillMetadataFromDirs(this.systemSkillPaths)
1740
+ : [];
1709
1741
  this.skillFingerprint = this.buildSkillFingerprint(skillMetadata);
1710
1742
  this.registerSkillTools();
1711
1743
  const agentId = this.parsedAgent.frontmatter.id ?? this.parsedAgent.frontmatter.name;
@@ -209,7 +209,17 @@ export const loadSkillMetadata = async (
209
209
  workingDir: string,
210
210
  extraSkillPaths?: string[],
211
211
  ): Promise<SkillMetadata[]> => {
212
- const skillDirs = resolveSkillDirs(workingDir, extraSkillPaths);
212
+ return loadSkillMetadataFromDirs(resolveSkillDirs(workingDir, extraSkillPaths));
213
+ };
214
+
215
+ // Scan an explicit list of absolute directories for `<name>/SKILL.md`
216
+ // manifests and return their metadata as `source: "repo"` skills (body
217
+ // read from disk on activation). Used both by `loadSkillMetadata` (after
218
+ // resolving repo skill dirs against the working dir) and directly for
219
+ // platform-shipped "system" skills whose source dirs are already absolute.
220
+ export const loadSkillMetadataFromDirs = async (
221
+ skillDirs: string[],
222
+ ): Promise<SkillMetadata[]> => {
213
223
  const allManifests: string[] = [];
214
224
 
215
225
  for (const dir of skillDirs) {