Skip to content

Compare MCP, CLI and API surfaces and agent skills in one run - #32

Open
RobertCrupa wants to merge 15 commits into
mainfrom
feat/surfaces-and-skills
Open

RobertCrupa wants to merge 15 commits into
mainfrom
feat/surfaces-and-skills

Conversation

@RobertCrupa

@RobertCrupa RobertCrupa commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

The runner can now compare how the agent reaches Apify (MCP, CLI, raw API) and which agent skills it has. Every model × surface × skill set is its own experiment on the same Langfuse dataset, so the compare view shows them side by side. Scenarios stay surface-agnostic.

Surfaces

  • surfaces/<name>.yaml: mcp (the default and the old behaviour), cli (Apify CLI plus mcpc connected to the Apify MCP server, in Bash, no MCP tools of the agent's own) and api (curl and WebFetch with APIFY_TOKEN).
  • Runner input surfaces, plus cliVersion and mcpcVersion to test another CLI or mcpc build without editing the surface file.
  • The CLI is pinned off PATH in the image, isolated and logged in per session. Evidence finds Actor runs that the CLI and mcpc start (including run ids from mcpc arguments).
  • Contract: surface classification and a new surface.used check. A scenario can limit itself with surfaces: [mcp].

Agent skills

  • Skills are not in this repo. The runner downloads apify/agent-skills once per run at the commit pinned in DEFAULT_SKILLS_REF (d522584, a full SHA, so no GitHub API call). A run without skills downloads nothing. Profiles list the skills a suite may use (agentSkills).
  • Order: skillsRef input, then SKILLS_DIR (a local directory), then the pin. An unknown skill name fails the run before any session starts; scenarios:check only checks the name format.
  • Runner inputs skillSets (none, a, a+b), skillCombinations (every subset) and skillsRef (take skills from another GitHub ref, to test a change before it is merged).
  • Claude Code starts with --setting-sources project, so personal skills do not leak into the none variant. The trace records agentSkills and skillsLoaded and gets the tag skills:<label>.

Profiles and judge

  • Profiles can name any MCP server (mcp), plus agentSkills and forcedFixAreas.
  • The judge reads the trajectory against the surface and skills the agent had. JUDGE_IMPL_VERSION 0.4.0 → 0.6.0, RUBRIC_VERSION 1203-draft-3 → 1203-draft-4.
  • The account run limit is now infrastructure (inconclusive), not a result.

Guard

maxSessions (default 400) stops a run with exit 15 when models × surfaces × skill sets × scenarios × repeats is larger. The full store-actors suite on 3 surfaces × 2 skill sets is 366 sessions.

Example input

{
    "datasetName": "store-actors",
    "surfaces": ["mcp", "cli", "api"],
    "skillSets": ["none", "apify-ultimate-scraper"],
    "subjects": ["compass/crawler-google-places"],
    "itemLimit": 2
}

Tests: shared 237, contract 42, report 11, judge 197, workflow-runner 108, tools 9. scenarios:check passes. npm run lint fails on main too (no ESLint config).

Dataset items can carry the MCP server the agent talks to (mcp: stdio or
http, ${VAR} placeholders for secrets) and the skills a suite may inject
(agentSkills). The agent span records mcpServer, agentSkills and
skillsLoaded. tool.called takes a name alternation safely and a max, so a
check without a name becomes a call budget. Contract 1.2.0.
mcp.ts resolves the item's server (Apify by default, narrowed with ?tools=)
into the Claude Code mcp-config, allowedTools patterns, secrets to redact
and a tools/list client for preflight and schema snapshots. skills.ts turns
the skillSets / skillCombinations inputs into variants; each (model, skill
set) is its own experiment, the skills are copied into the session as
project skills and the session runs with --setting-sources project so the
developer's own skills never leak into a 'none' variant. OUTPUT lists the
variants by pass rate. The image installs @notionhq/notion-mcp-server and
copies skills/.
The sync stamps the profile's MCP server and injectable skills on every
item and validates that each skill exists under skills/. The judge honours
a profile's own forcedFixAreas mapping (judge impl 0.5.0).
… check

A surface (surfaces/<name>.yaml) says how the agent reaches Apify: MCP, the
CLI or raw HTTP to the API. Contract 1.3 adds its schema, the span fields
surface, surfaceKind, surfacesUsed and cliVersion, an item-level surfaces
filter, the run origin in the evidence, and a surface.used check that flags
when an agent falls back to another surface.
Each run takes a surfaces input (surfaces/<name>.yaml) and runs one
experiment per model x surface x skill set. The surface builds the session:
MCP tools or none, Bash, WebFetch, a pinned Apify CLI on PATH (cliVersion
overrides it for testing a build), extra env and a prompt suffix.

Evidence no longer depends on MCP: runs are found in CLI and API output
(every Console and API URL form, run objects in JSON), runs that no output
names are found by the session marker in the curl user agent, and run
input comes from the run's INPUT record, so apify.input checks work on
every surface. Skills now go into the working directory, the only setting
source. maxSessions refuses runs that grow too large (exit 15).
…ub ref

Vendors four skills from apify/agent-skills at d522584 (the deprecated
apify-generate-output-schema is left out). The skillsRef input downloads a
public GitHub ref once per run and uses it as the skills root, so a skill
author can evaluate a branch without a runner build; the run records the
resolved commit.
…nt had

The facts block names the agent setup (surface, CLI version, injected and
loaded skills) and the Apify surfaces the agent used, and the prompt tells
the judge to read tool choices against it. The store profile gains the
surface and skill-instructions fix areas, lists apify-ultimate-scraper as
an injectable skill, and a failed surface.used check forces the surface
fix area. RUBRIC_VERSION 1203-draft-4, JUDGE_IMPL_VERSION 0.6.0.
…ment surfaces

The image installs apify-cli 1.10.0 in /opt/apify-cli (CLI_ROOT), not on
PATH: only sessions of a CLI surface get it. Other versions install at run
time. surfaces/ is copied next to skills/ (SURFACES_DIR). The README says
how to compare surfaces and skill sets and how to test a CLI, skill, MCP or
API change.
Scenarios say what the user wants, not how to reach Apify, so one scenario
runs on every surface; surfaces: [mcp] limits the few that check an MCP
tool. Adds the surface.used check to the table.
…runs

Found in a local smoke run:
- Apify CLI 1.10.0 ignores APIFY_TOKEN in the environment; it reads only its
  own login (on macOS the Keychain). The runner now logs each CLI surface in
  once (apify login --token, file credentials, throwaway HOME) and every
  session copies that login through its own apify wrapper, so a session never
  reaches the developer's login and the cli surface works in the cloud.
- On surfaces without the CLI the wrapper makes apify a missing command, as
  in the image.
- apify call -s prints only items. The wrapper preloads a module that adds the
  session marker to the CLI's api.apify.com user agent, and the session-window
  lookup accepts CLI runs as well as API runs.
- Sessions always run with --strict-mcp-config: without an MCP config, a
  local session picked up the developer's claude.ai connectors.
The cli surface now has mcpc (0.7.0) next to the Apify CLI. Each session
runs it through a wrapper with its state in the session directory, connected
to the Apify MCP server as @apify before the agent starts and closed after
(every mcpc session holds a bridge process). mcpc is installed without
optional dependencies, which leaves out its keychain module, so its
credentials stay in that file on every platform. mcpcVersion tests another
build; the preflight connects once.

mcpc commands count as the cli surface. Its readable tool output names only
datasetId=..., so the evidence also finds runs through the dataset that
recorded them (actRunId) and reads runId from mcpc --json output.
An agent polled its run with mcpc @apify tools-call get-actor-run
runId:='"..."' and timed out before any output named the run, so the
evidence missed it.
A cloud matrix on a 5-run account had 86 of 126 sessions where the platform
refused the agent's Actor run ("exceed your limit of 5 concurrent Actor
runs"); they were scored as wrong-actor or fail against the surface.
- A session whose tool output shows that refusal is now inconclusive
  (infra reason, contract evidence session.runLimitHit).
- Preflight warns when concurrency + this run exceed the account's
  maxConcurrentActorJobs and suggests a concurrency that fits.
- After the evidence is frozen, runs the agent left running are aborted:
  they only cost money and hold run slots.
- scenarioIds input: run only the named scenarios.
@apify-service-account apify-service-account added the tested Temporary label used only programatically for some analytics. label Oct 16, 2026
@apify-service-account apify-service-account added the t-ai Issues owned by the AI team. label Oct 16, 2026
…ndoring

The runner downloads apify/agent-skills once per run at the commit pinned in
DEFAULT_SKILLS_REF. skillsRef and SKILLS_DIR still override it, and a run
without skills downloads nothing. A full commit skips the GitHub API call.
The pinned commit is byte-identical to the copy this removes.

scenarios:check now only checks that agentSkills entries are skill names;
the runner fails on an unknown name before any session starts.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

t-ai Issues owned by the AI team. tested Temporary label used only programatically for some analytics.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants