Configuration files
Plugin configuration
plugin.json
The .tessl-plugin/plugin.json file is the configuration file for a plugin. It includes important metadata that helps Tessl identify, version and describe plugins.
Plugins can contain four types of content:
- rules: Rule files (markdown) that provide subjective guidance and instructions to agents, such as coding standards, best practices, and organizational policies.
- skills: Skill files (SKILL.md format) that extend AI coding agents with specialized knowledge, workflows, or tool integrations. Skills follow the Agent Skills Specification.
- MCP servers: Model Context Protocol servers, declared in a bundled
.mcp.jsonfile, that connect agents to external tools and services during a session. - hooks: Deterministic commands that run automatically in response to agent events (for example, running a linter after an edit), declared with the
hooksandnativeHooksfields.
A plugin can contain one or more of these content types. For example, a plugin might have only rules, only skills, only MCP servers, or any combination of the four.
Here's an example .tessl-plugin/plugin.json:
The following sections show examples of plugins with different content types. Remember that plugins can contain rules, skills, MCP servers, or hooks.
For example, here's what the ./tessl-plugin/plugin.json for a private, rules-only plugin might look like:
Plugins can also contain skills, which are reusable capabilities that extend AI coding agents. Here's an example of a plugin with skills:
And here's a plugin that combines rules, and skills:
.tessl-plugin/plugin.json supports the following fields:
name (string, required): Name for the plugin in workspace/plugin-name format
version (string, required): Semantic version of the plugin
description (string, required): Brief description of the plugin
private (boolean, optional): Controls plugin visibility in the registry. Set to false to make the plugin publicly discoverable by all users, or true to restrict access to your workspace only. Defaults to true if not specified. Note: Authentication with tessl login is required to publish plugins regardless of this setting.
rules (string or string[], optional): Path to a directory of rule files, or an array of specific .md file paths. Rules are always-loaded guidance for agents — the plugin.json equivalent of steering. Omit to use convention discovery, which scans ./rules/ for .md files automatically.
skills (string or string[], optional): Path to a directory containing skill subdirectories, or an array of specific skill directory paths. Each skill directory must contain a SKILL.md file. Skills are reusable capabilities that extend AI coding agents with specialized knowledge, workflows, or tool integrations. Omit to use convention discovery, which scans ./skills/ for subdirectories containing SKILL.md automatically.
mcpServers (string, optional): Pointer to the plugin's bundled .mcp.json file at the plugin root. The value is fixed — either ".mcp.json" or "./.mcp.json". Declaring it is optional and non-gating: the .mcp.json file is discovered by convention at the plugin root whether or not the manifest mentions it, so the field only lets authors be explicit. Note that this string field is a pointer to the file — not to be confused with the mcpServers map inside the .mcp.json file itself (see below), which lists the actual servers. See Bundled .mcp.json for the file's shape.
hooks (object, optional): Cross-agent (generic) hooks, deterministic commands that run in response to agent events. A map of event name to hook groups, authored once and dispatched to every supported agent. See Hooks for the shape.
nativeHooks (object, optional): Per-agent (native) hooks, hook entries written in an individual agent's own hook format, keyed by agent id. Use this when a hook needs an agent-specific event or field the generic hooks field can't express. See Hooks.
A note on plugin.json validation:
rules,skills,mcpServers,hooks, andnativeHooksare all optional. A plugin with none of these relies entirely on convention discovery. There is no requirement that at least one content type be explicitly declared.
Bundled .mcp.json
A plugin declares MCP (Model Context Protocol) servers by adding a .mcp.json file at the plugin root — the same format Claude Code already reads in a repository. This is the file the mcpServers pointer field above points at; it is discovered by convention even when the field is omitted. (The pointer field shares the mcpServers name with the map inside this file, described below, but the two are distinct: the field names the file, the map lists the servers.)
Note: This is a different file from the
.mcp.jsonTessl writes into a consuming project (see MCP configuration files below). The plugin-bundled.mcp.jsonis authored and shipped inside the plugin package to declare the servers it provides. The project.mcp.jsonis generated by Tessl in the consuming repository to wire the agent up to Tessl's owntessl mcpserver. They share a name and format but serve different purposes.
The file has a top-level mcpServers map of server name to server config. Each config is one of two transport types, selected by its type field:
stdio — a server the agent launches as a local subprocess:
type(string, required):"stdio".command(string, required): The executable to run.args(string[], optional): Arguments passed to the command.env(object, optional): Environment variables (string values) set for the process.
http — a server reachable over HTTP:
type(string, required):"http".url(string, required): The server endpoint. Must be anhttporhttpsURL.headers(object, optional): HTTP headers (string values) sent with each request.
How bundled servers are installed
When a plugin is installed, Tessl materialises its bundled servers into each supported agent's own native MCP configuration — the file that agent already reads, in the format and location it expects. By default servers are written into the project; installing with --global writes them into each agent's user-scope configuration instead.
Tessl reconciles these servers on every install and prunes them when the plugin is uninstalled. Your own hand-added MCP servers are always left untouched. If you hand-edit a server Tessl wrote, it stops recognising the entry as its own and leaves it in place with a warning rather than overwriting it.
Hooks
Hooks are deterministic commands that run automatically in response to events within an agent harness: running a linter after an edit, validating a spec when a route handler changes, or posting a summary when a session ends. A plugin declares hooks in .tessl-plugin/plugin.json using one of two tiers, expressed as two separate top-level fields:
hooks: the cross-agent (generic) tier. You author one command against Tessl's generic event schema, and Tessl dispatches it to each agent that supports the event. This is the paved road: use it whenever the hook's logic is agent-independent (a linter, a validator, a notifier) so it works across agents without per-agent authoring. Not every agent implements every event; an event an agent doesn't support is skipped for that agent (see the note below).nativeHooks: the per-agent (native) tier. You write hook entries in an individual agent's own hook format, keyed by agent id. This is the escape hatch: use it when a hook needs an agent-specific event or field that the generic schema can't express. Tessl carries these entries through near-verbatim. It does not own the shape, so each agent validates its own entries.
A plugin can use either tier or both.
Cross-agent hooks (hooks)
The hooks field is a map of event name to an array of hook groups. The supported event names are:
PreToolUse: before the agent runs a tool.PostToolUse: after a tool call completes.UserPromptSubmit: when the user submits a prompt.SessionStart: when a session begins.Stop: when the agent finishes responding.
Note: Agents vary in which of these events they implement. Gemini, for example, supports only
PreToolUse,PostToolUse, andSessionStart. When an agent does not implement a declared event, that event is skipped for that agent (the rest still install), so prefer events supported by the agents you target.
Each hook group has an optional matcher and one or more command hooks:
matcher(string, optional): A tool-name filter: a single tool name (Bash), a pipe-separated list (Edit|Write), or a regex. Omit it to fire the group for every tool call. It only narrows tool-call events (PreToolUse,PostToolUse); onUserPromptSubmit,SessionStart, andStopthere is no tool name to match, so omit it.hooks(array, required): One or more command hooks (see below).
A command hook has a type of "command" and takes one of two forms, selected by whether args is present:
- Exec form (
argspresent):commandis the program to spawn (argv[0], e.g.bashornode) andargsare its arguments. The program is spawned directly, with no shell, so pipes, globs, and&&are not interpreted. - Shell form (
argsabsent):commandis a full command string run viash -c, so shell features like pipes, globs, and&&work.
Command hook fields:
type(string, required): Always"command".command(string, required): The program (exec form) or the shell command string (shell form).args(string[], optional): The program's arguments. Its presence selects exec form; its absence selects shell form.[]is a valid exec form (a program with no arguments).env(object, optional): Environment variables for the hook. A value can be a static string, or{ "fromEnv": "VAR" }to forward the named variable from the agent's shell environment at hook-fire time.
To reference a file bundled inside your plugin, use the ${TESSL_PLUGIN_DIR} token: it is substituted across command and args with the plugin's installed location. A bare relative path is not anchored to the plugin; it resolves against the event's working directory (the project root) at spawn time, like any argument. In shell form, quote the token ("${TESSL_PLUGIN_DIR}/lint.sh") so paths with spaces survive.
Note: Shell form runs under POSIX
sh -c(on Windows this relies on git-bash). PowerShell andcmd.exeare not used.
Per-agent hooks (nativeHooks)
The nativeHooks field is a map keyed by agent id (for example claude-code). Each agent's value is that agent's own native hook config, keyed by the agent's own event names, which Tessl passes through near-verbatim. Because Tessl does not own this shape, refer to the target agent's own hook documentation for the events and fields it accepts.
${TESSL_PLUGIN_DIR} works here too, but is resolved at install time (not hook-fire time) into a path the agent will find when the hook fires: anchored to the agent's project-directory variable or a repo-relative path in project scope, and an absolute path in global scope. Resolved paths that would escape the plugin directory are rejected.
How hooks are installed
When a plugin is installed, Tessl materialises its hooks into each supported agent's own native hook configuration: the file that agent already reads.
- Cross-agent hooks are installed as a
tessl hook rundispatch entry: the agent invokes Tessl when the event fires, and Tessl runs your declared command, translating the agent's native event payload to and from the generic schema. - Per-agent hooks are written into the agent's config as your own entries (after
${TESSL_PLUGIN_DIR}is resolved).
By default hooks are written into the project; installing with --global writes them into each agent's user-scope configuration instead. Agents without a user-scope hook location are the exception: OpenHands, for instance, has no global hook config, so --global skips it with a warning rather than writing an unread file. Tessl tracks the entries it writes and prunes them when the plugin is uninstalled, leaving your hand-added hooks untouched.
Project configuration
tessl.json
The tessl.json file is the manifest for your repository's plugin dependencies. It specifies which plugins are installed and their versions:
This file is created automatically when you run tessl init, tessl install, or tessl project create. Tessl manages the dependencies in this file as you install or uninstall plugins.
Verify block
The optional verify block in tessl.json configures tessl change verify. It groups the standalone verifier JSON files in your repository, points each group at the files it should check, and sets the severity of its findings. The block is the runtime source of truth for verification. tessl change verify reads it plus the verifier files its groups reference. See Verifiers overview for when to use a verifier and Verify agent changes in CI for a complete setup walkthrough.
verify
groups(object, optional): Named groups of verifiers. Keys are arbitrary group names; values are group entries (see below).
Group entry
Each entry under verify.groups accepts:
path(string or string[], required): Path or glob (or list of them) to the verifier JSON files this group runs.level(string, required): Severity for the group's findings — one ofinfo,warn, orerror.errormakes a failing check blocking in CI;warnandinfoare advisory.include(string[], optional): Globs of files this group targets. Files added later that match are covered automatically.exclude(string[], optional): Globs of files to drop from the group's targets.content(object, optional): Content-based file filtering (see Content predicate).enabled(boolean, optional): Set tofalseto skip the whole group.verifiers(object, optional): Per-verifier overrides, keyed by verifier file name. Each override acceptsinclude,exclude,content,enabled, andlevel— letting the same verifier be advisory (warn) in one project and blocking (error) in another without editing the verifier JSON.
Content predicate
The content field (on a group or a per-verifier override) keeps or drops files by what they contain, before the judge runs:
containsAny(string[]): Keep files containing at least one of these literal strings.containsAll(string[]): Keep files containing every one of these literal strings.regexAny(string[]): Keep files matching at least one JavaScript regular-expression pattern.regexAll(string[]): Keep files matching every JavaScript regular-expression pattern.notContainsAny(string[]): Drop files containing any of these literal strings.notRegexAny(string[]): Drop files matching any of these JavaScript regular-expression patterns.
Verifier JSON files
The files referenced by a group's path describe the invariant to check. A verifier file accepts:
name(string, required): Short human label for CLI output. The filename remains the stable identifier.instruction(string, required): What the judge should check for.relevant_when(string, required): When the verifier applies.context(string, required): Background the judge needs to score the file.scope(string, required): The unit the judge evaluates —file,paths, ordiff.scope_reason(string, required): Why that scope is the right target shape.checklist(array, required): One or more check items, each with aname, arule, and an optionalrelevant_when.references(array, optional): Informational provenance for where the rule came from.project_when(object, optional): A precondition evaluated once per run (no LLM call); when it is false the verifier is skipped entirely. Supportspackage_json_has_dep,file_exists,glob_matches, and the combinatorsany_of,all_of, andnot.covered_by(array, optional): Links to existing lint rules or tests that already enforce the rule. When every link is fresh the judge is skipped and the verifier reports ascovered; when any link is stale the judge runs anyway and the staleness is surfaced.
Validate a verifier file with tessl change verify lint <file>, and preview which files a group matches with tessl change verify --dry-run --all --show-files before running judge calls. Verifiers are separate from Tessl Code Review, so add their workflow only when the repository needs an invariant check alongside pull request review.
Actions, triggers, and schedules
Three optional blocks in tessl.json declare the repository's loops. Tessl syncs them when you push to the default branch. See Automations for how loops, actions, triggers, and runs fit together.
actions(object, optional): named actions, keyed by a lowercase kebab-case slug. Each declares what a run does.triggers(object, optional): named event triggers. Each starts an action when a provider event matches.schedules(object, optional): named schedules. Each starts an action on a cron. The full reference is in Schedules.
A trigger or schedule entry names its action in one of three forms: the name of a declared action ("action": "triage"), that name with overrides ("action": { "use": "triage", "inputs": { ... } }), or an action written inline. Every entry that names the same declared action belongs to one loop.
A minimal example, with one action started by a GitHub event and by a schedule:
Run tessl doctor --automations to validate all three blocks before you push.
Action entry
skill(string, required): the plugin to run, as a registry refworkspace/plugin[@version][#skill]or a repo-localfile:pathrelative to the directory oftessl.json.environment(string, optional): the environment a run executes in, by name. Without one, a run gets a read-only GitHub token.agent,model,effort(string, optional): which agent runs the skill, its model, and how hard it thinks.inputs(object, optional): skill input values, as string keys and string values.instructions(string, optional): free text appended to the skill prompt.baseBranch,workdir(string, optional): the branch a run checks out and the subdirectory it runs in.timeout(string, optional): the run's time budget, such as90s,15m, or2h.concurrency(object, optional): how runs of the action share work, withkeys,debounce, andmode.description(string, optional): what the action is for.
A declared action can also take params, overlays, and selectOverlays to choose its settings from the inputs a run passes it. See Choose settings from an action's inputs.
Trigger entry
on(string[], required): the events that fire the trigger, as<provider>:<event>, for examplegithub:issues.labeled,github:pull_request_review.submitted, orlinear:Issue.update. Every event in the list must have the same shape, the part before the first dot.when(object, optional): conditions that must all hold. A key is a payload path writtenevent.…, and its value is a literal or an operator object:equals,in,contains,matches,mentions,exists,not,any, ornone. Withoutwhen, every event inonfires the trigger.action(string or object, required): the action to run, in one of the three forms above. Map event values to inputs with{{ event.… }}ininputs.concurrency(object, optional): the same shape as an action's.description(string, optional): what the trigger is for.
A trigger's name is its identity: renaming it deletes the old trigger and creates a new one.
Tessl project link
tessl.json stores the Tessl configuration for this repository, including its project link.
A Tessl project gives Tessl a stable place to attach eval runs and other repository-connected data, so your results stay tied to your codebase over time.
For example:
If the local project link is missing or no longer correct, use tessl project link or tessl project repair. See Manage projects from the CLI.
Region
The optional region field selects the Tessl region for every tessl command run inside the repository. Valid values are us and eu:
The --region flag and the TESSL_REGION environment variable take precedence over this field. Without any of them, the CLI uses the region you last logged in to. See Using the EU region.
Project Mode (Managed vs Vendored)
The mode field in tessl.json controls how plugin content is managed in your repository. You can choose between two modes depending on your workflow and requirements:
Managed mode
Default for existing repositories - plugin contents are gitignored like node_modules
Behavior:
- plugin contents in
.tessl/plugins/are automatically added to.gitignore - plugins are reinstalled from the registry based on
tessl.json - Works like package managers (npm, pip) - dependencies not committed
- Keeps repository clean and small
- Team members run
tessl installafter cloning the repository
Best for:
- Repositories with frequent plugin updates
- Teams who prefer lighter repositories
- Standard development workflows with internet access
Vendored mode
Default for new repositories - plugin contents are committed to your repository
Behavior:
- Plugin contents in
.tessl/plugins/are committed to version control - Exact plugin versions are checked into the repository
- Works offline without registry access
- Team members get plugins automatically when cloning
- Ensures reproducible builds in all environments
Best for:
- Air-gapped or restricted network environments
- Repositories requiring complete offline capability
- Ensuring exact reproducibility without external dependencies
- Compliance requirements for vendoring all dependencies
Switching modes
You can change modes at any time by updating the mode field in tessl.json:
After changing the mode:
- Tessl will automatically update
.gitignoreaccordingly - In managed mode,
.tessl/plugins/will be added to.gitignore - In vendored mode,
.tessl/plugins/will be removed from.gitignore - Commit the changes to apply the new mode for your team
Default behavior
- New projects (running
tessl initin a fresh project): Defaults to vendored mode - Existing projects (running
tessl initin a project with existing plugins): Defaults to managed mode - You can explicitly set the mode in
tessl.jsonto override the default
.tessl directory
The .tessl directory contains Tessl's configuration and cached data:
The .tessl/.gitignore file is automatically managed based on your project mode:
- Managed mode: The
plugins/directory andRULES.mdare added to.gitignore(not committed to version control) - Vendored mode: The
plugins/directory is removed from.gitignore(committed to version control), butRULES.mdremains ignored as it's generated from plugin content
Plugin directory structure
Below we see a plugin structure as it's being authored:
evals/sits at the plugin root, not under each skill. Oneevals/holds the scenarios for every skill in the plugin- evals/ is only present if scenarios were generated
- skills/<your-skill-name>/SKILL.md is suggested structure, otherwise plugin.json will need a specific pointer
- tessl.json links the directory to a Tessl project, which evaluations need
Agent rule files
Tessl creates and updates rule files for AI coding agents to help them understand your repository context and installed plugins. The location and format of these files varies by agent:
- Cursor:
.cursor/rules/tessl__*.mdc- plugin-specific rules (auto-generated, not committed to git).cursor/rules/tessl_context.mdc- Instructions for gathering context from Tessl MCP
- Claude Code:
CLAUDE.md- Context file with instructions for gathering context from Tessl MCP.tessl/RULES.md- Consolidated rules from all installed pluginsAGENTS.md- If this file exists, Tessl adds a reference to.tessl/RULES.md
These files are created when you run tessl init --agent <agent-name> or when Tessl auto-detects an agent in your repository.
MCP configuration files
When configuring AI agents, Tessl adds MCP (Model Context Protocol) server configuration to connect the agent to Tessl's MCP server. The location varies by agent:
- Cursor:
.cursor/mcp.json - Claude Code:
.mcp.jsonin the repository root
These files configure the agent to run tessl mcp start as an MCP server, enabling the agent to access Tessl's tools and context.
This is the agent host configuration Tessl writes into a consuming project. It is distinct from the bundled .mcp.json a plugin author ships inside their package to declare the servers the plugin provides.
AGENTS.md
The AGENTS.md file provides repository context to AI coding agents. This file is similar to CLAUDE.md and other agent context files used by various AI coding assistants.
Note: Tessl does not create or manage AGENTS.md directly. However, if AGENTS.md exists in your repository, Tessl will automatically add a reference to .tessl/RULES.md when you configure an agent with tessl init. This allows your agent to access plugin-specific guidance and context.
If you're using AGENTS.md in your repository:
- Add your own repository context, coding conventions, and patterns
- Tessl will append a section linking to
.tessl/RULES.md(marked with<!-- tessl-managed -->) - The content is used by AI agents during code generation
.tesslignore
Exclude files from plugin validation and packing.
Purpose
The .tesslignore file allows you to exclude files from orphaned file warnings and prevent them from being included in published plugin packages.
Location
Place a .tesslignore file in the root of your plugin directory (same level as .tessl-plugin/plugin.json).
Syntax
Uses gitignore-style patterns:
Default ignored files
These files are always ignored, even without a .tesslignore file:
AGENTS.mdCLAUDE.mdGEMINI.md
Important rules
- Links to ignored files cause errors: If your docs link to a file that's in
.tesslignore, validation will fail. This prevents broken links in published plugins. - Manifest files can't be ignored: You cannot put
docs,rules, orskillsentrypoints in.tesslignore.
Example .tesslignore
Code Review configuration
.tessl-code-review.yml
A repository-owned YAML profile routes Tessl Code Review lenses to parts of a change and sets the review's finding policy. How the file is discovered depends on how the review runs:
- The Tessl Review GitHub App discovers it automatically. The App decides presence on the repository's default branch: it looks for
.tessl-code-review.ymlat the root of the default branch, and if the file is not there, the review runs the built-instandardprofile and nothing in the file matters, including a copy added in the pull request under review. Once the file exists on the default branch, the App reads its content at the pull request's head commit, so an edit to the file takes effect on the pull request that makes it. Because the content comes from the pull request head, theCODEOWNERSwarning below applies to the App too. Under the App, the file name is fixed: only.tessl-code-review.ymlat the repository root is discovered. - The CLI and the unsupported GitHub Action do not discover it. They run it only when a command or the Action names its path. Pass it to the CLI with
--profile ./.tessl-code-review.yml, or to the Action withprofile: ./.tessl-code-review.yml. For the CLI and the Action the name is a convention, and any.ymlor.yamlpath works.
The file is a block-style YAML mapping. It must set schemaVersion: 1 and a non-empty, ordered lenses list; the rest is optional.
The Tessl Review GitHub App and the GitHub Action both read this file's content from the pull request head, so a contributor who can push to the pull request can also change its lens routing or requestChangesAt in the same change and weaken the review. A ref pointing at a repository-local SKILL.md reads that file's instructions from the pull request head too, so protecting only this file leaves the referenced local lenses reachable by the change author. To require separate approval for review-policy changes, protect this file and every repository-local SKILL.md its lenses reference with CODEOWNERS, then enable required code-owner approval in the repository's branch protection or ruleset. A lens pinned to a registry reference is fixed by its version and needs no such protection.
Top-level keys
schemaVersion(required): must be1.lenses(required): an ordered, non-empty list of lenses. See Lens entries. At most 12 lenses run, unless your organization has a different limit. Lenses whoseglobsmatch no changed file do not run and do not count toward that limit.effort(optional): how hard every lens thinks:low,medium,high, oradaptive.adaptiveusesmediumfor a full review andlowfor a re-review. A single lens can override it with a fixed level. See Effort.reviewMode(optional): blocking strength, one ofstrict,standard, orrelaxed. Omit it forstandard.strictblocks every eligible finding,standardrequires Medium likelihood strength, andrelaxedrequires High likelihood strength. Low-confidence fresh findings are omitted in every mode, and pre-existing findings never block.requestChangesAt(optional): the minimum consequence severity eligible to block, one ofcritical,major,minor, ornit. Omit it formajor. Each consequence level above this baseline adds one blocking-strength point.ignore(optional): repository-relative POSIX globs excluded from every lens. See Ignore.
Lens entries
Each entry in lenses is a mapping:
ref(required): a localSKILL.mdpath or skill directory, an installed skill name, or a registry referenceworkspace/plugin[@version]#skill-name. Local paths are resolved relative to the profile and must stay inside the repository. Pin every registry reference so the review does not change when a plugin publishes a new version.globs(optional): repository-relative POSIX patterns selecting the files this lens sees. A lens withoutglobsapplies to the whole change.effort(optional): this lens's own effort, a fixedlow,medium, orhigh, overriding the file-leveleffort. A single lens cannot takeadaptive; only the file-leveleffortand the CLI/Action override accept it.
Lens order is preserved. Each lens receives only its selected files. If no lens matches the change, the review returns a skipped result without implying approval.
Globs
Globs are case-sensitive and repository-relative. Each list must contain at least one positive pattern. Patterns are evaluated in order: a positive pattern includes a path, a pattern prefixed with ! excludes it, and a later positive pattern can include it again. For a renamed file, a lens is selected when either the old or new path matches.
Ignore
The top-level ignore list bounds which paths any lens reviews, so generated code, lockfiles, snapshots, or vendored directories are excluded once instead of negated in every lens. Each pattern is positive (it excludes what it matches), so a leading ! is rejected, and a bare ** that would exclude everything is rejected. At most 64 patterns are allowed. ignore overrides anything a lens selected, and a lens's own globs still narrow it further. Because a file is selected when either rename endpoint matches, a file renamed into an ignored path is still reviewed. ignore bounds which paths a lens reviews; it does not keep a path's contents out of a model's context.
Effort
effort at the file level applies to every lens; on a single lens it makes that one think harder or less hard than the rest. A lens without its own effort uses the file's; with neither, the model applies its own default. The CLI --effort flag and the Action effort input apply to every lens and take precedence over both.
The file-level effort, --effort, and the Action effort input each accept low, medium, high, or adaptive. adaptive uses medium for a full review and low for a re-review, which is how the built-in standard profile scales by round. A single lens's own effort must be a fixed low, medium, or high; adaptive is rejected there, because the round-dependent policy is set once for the whole review rather than per lens.
For the complete behavior and how the file interacts with --skill, see the tessl code review CLI reference. For routing worked into a full setup, see Configure reviews with .tessl-code-review.yml.
Eval scenario configuration
scenario.json
scenario.json lives next to task.md and criteria.json in a scenario directory. It declares what the platform sets up in the working directory before an agent solves the task — fixtures to install, local files to copy in, and setup scripts to run.
scenario.json is optional. With nothing but task.md and criteria.json, conventional defaults take over (see Conventional defaults).
Fields
description(string, optional): Human-readable summary of the scenario.fixtures(object, optional): Named external content the platform installs into the working directory. Keys are arbitrary names; values are fixture objects (see Fixture types). Order-independent.include(string[], optional): Paths inside the scenario directory copied into the working directory at the same relative path. Each entry must be a directory.setup(string[], optional): Paths to scripts inside the scenario directory, run in order after fixtures and includes are installed.
Fixture types
Two fixture types are supported in scenario.json:
commit — snapshot of a git repo at a specific ref.
repoUrl(string, required): HTTPS clone URL.ref(string, required): Commit SHA or any ref resolvable in the remote.installPath(string, optional, default.): Write destination, confined to the working directory.include(string[], optional): Glob patterns of paths to keep from the snapshot. When it lists at least one glob, only matching paths are packed and installed, and it is the only field that shrinks the uploaded archive. When omitted or empty, the whole repository tree atrefis packed. Applied beforeexclude.exclude(string[], optional): Glob patterns of paths to remove from the snapshot after it is installed. It does not reduce the size of the upload.
Tessl builds one archive per distinct ref value. Fixtures share an archive only when their ref strings are identical, so a full SHA and an abbreviated SHA of the same commit produce two archives. When every fixture on a ref sets a nonempty include, that archive holds the union of their globs. When any fixture on the ref has no include or an empty one, the whole repository tree is packed for that ref, and the include globs on its other fixtures do not shrink it.
A commit fixture is installed as a fresh repository with a single commit. The source repository's earlier commits, branches, and tags are not available.
directory — content from a local path relative to the scenario.
path(string, required): Read source, resolved relative to the scenario directory but not confined to it; it may resolve to a location outside the scenario directory, including parent traversal.installPath(string, required): Write destination, confined to the working directory.include(string[], optional): Glob patterns of files to keep from the directory. When omitted, the whole directory is installed; when present, only matching files are kept. Applied beforeexclude.exclude(string[], optional): Glob patterns of files to remove from the directory. Applied afterinclude.
Conventional defaults
Without explicit configuration, two on-disk conventions take effect:
- If a
resources/directory sits alongsidetask.md, it's automatically copied into the working directory atresources/. Equivalent to"include": ["./resources"]. - If a
setup.shfile sits alongsidetask.md, it's automatically run after fixtures and includes install. Equivalent to"setup": ["./setup.sh"].
A scenario directory containing only task.md, criteria.json, resources/, and setup.sh therefore needs no scenario.json at all.
Working directory assembly order
- Install fixtures (all entries, order-independent).
- Copy includes (paths preserved).
- Run setup scripts in
setuporder, with auto-detectedsetup.shlast.
Legacy format
A singular fixture object (without the s) at the top level is read as a commit fixture named codebase for backward compatibility with scenarios generated before the fixtures plural form. Prefer the plural form for new scenarios.
User preferences
Tessl stores user preferences globally to customize your experience. You can view and modify these preferences using the tessl config commands.
Available preferences
shareUsageData- Whether to share telemetry and usage data with Tessl (defaults totrue)agents- Which agents to configure ontessl init(default to auto-detect when empty); see supported agents intessl init --help.
Managing preferences
View all current preferences:
View a specific preference:
Set a preference:
Opting out of telemetry
To opt out of sharing telemetry and usage data:
For more information about data collection, see Sharing Usage Data.