Goal manifest
Every key in kortix.yaml, the goal manifest: agents, connectors, triggers, sandbox and env.
kortix.yaml at the root of the goal's repository is the goal manifest; the UI calls
it the project manifest. It says which agents may run and what each may touch, which connectors and triggers the
goal has, which machine sessions start on and which secret names the goal expects. This page is the reference for
version 2, which every new goal uses.
Check the file
The schema is public. The first line of the example below points your editor at it. kortix validate checks the
file in a clone, and kortix schema --version 2 prints the schema. The same check runs when a change request
merges, and an invalid manifest blocks the merge. Top-level keys Auto Lab does not know are skipped rather than
rejected, so the file can carry your own notes. A version higher than 2 is refused. Older goals may still have a version 1 kortix.toml; new goals
never do.
A full example
# yaml-language-server: $schema=https://app.auto-lab.ai/schema/kortix.v2.schema.json
kortix_version: 2
default_agent: kortix
env:
required: [CRM_API_KEY]
optional: [FORM_WEBHOOK_SECRET]
sandbox:
default: data
templates:
- slug: data
name: Data tools
dockerfile: .kortix/Dockerfile
cpu: 4
memory: 8
agents:
kortix:
connectors: all
secrets: all
skills: all
kortix_cli: all
reporter:
connectors: [hubspot, gmail]
connectors_required: [hubspot]
secrets: [CRM_API_KEY]
skills: [weekly-brief]
kortix_cli: [project.read, project.gitops.push]
connectors:
- slug: hubspot
provider: composio
app: hubspot
policies:
- match: "*delete*"
action: block
- slug: gmail
provider: composio
app: gmail
authorization_strategy: user
sensitive: true
policies:
- match: "gmail.*send*"
action: require_approval
policy:
default_mode: risk
triggers:
- slug: monday-brief
name: Monday brief
type: cron
agent: reporter
cron: "0 0 8 * * 1"
timezone: Europe/London
prompt: Draft this week's competitor brief and send it to me for review.
- slug: new-lead
type: webhook
secret_env: FORM_WEBHOOK_SECRET
filter:
body.type: lead
prompt: |
New lead from {{ body.email }} at {{ body.company }}.
Check it against our qualification rules.
The example assumes the repository has .kortix/opencode/agents/reporter.md, a weekly-brief skill and both
secrets.
Top-level keys
| Key | Required | Default | What it sets |
|---|---|---|---|
kortix_version | yes | Must be 2. | |
default_agent | yes | The agent a session or trigger runs when it names none. Must be a declared agent that is not disabled. | |
agents | yes | What each agent may touch. At least one agent. | |
project | no | name and description, for people reading the file. | |
env | no | The secret names the goal expects. | |
sandbox | no | Auto Lab default | Machine images and which one sessions use. |
connectors | no | The tools agents can call. | |
policies | no | Tool rules that apply across every connector. | |
policy.default_mode | no | allow_all | What happens to a tool call no rule matches. |
triggers | no | Scheduled and webhook automation. | |
opencode.config_dir | no | .kortix/opencode | Where sessions read agents and skills. Leave it: the Chat and learning always use .kortix/opencode. |
runtime | no | opencode | Leave it unset. pi is an experiment behind a feature flag. |
apps | no | Only read when the Apps feature is switched on for the goal. It is off by default. |
A channels or sandboxes key is an error. Channels are connected in Settings › Channels, not in the file.
agents
agents maps an agent name to what it is allowed to touch. It holds no behaviour. The prompt, model, mode,
temperature and tool permissions live in the agent's own file, .kortix/opencode/agents/<name>.md, and the map key
must match that file name. Putting one of those fields in kortix.yaml is an error. See
Agents and skills.
| Field | Default | Notes |
|---|---|---|
| (name) | Lowercase letters, digits, - and _, up to 128 characters. | |
enabled | true | false keeps the agent from starting. |
connectors | none | Connector slugs it may call, or all. |
connectors_required | none | Connectors that must have a working connection before a session starts. Each must also be in connectors. |
secrets | none | Secrets it receives, by name, or all. |
skills | none | Skills it may load, by name, or all. |
kortix_cli | none | What it may do with the kortix CLI and the API, as a list of permissions, or all. |
sandbox | The slug of the sandbox template its sessions start on. | |
workspace | branch | branch gives its sessions the repository on their own branch. runtime and read start them without a copy of the repository. |
Grants are deny-by-default. A field you leave out means nothing is granted. all never lifts an agent above the
person who started it. kortix_cli takes goal-level permissions only, such as project.read,
project.gitops.push or project.trigger.read; run kortix validate --scopes for the full list. Organization
permissions can never be granted to an agent. Even with project.gitops.merge, a session cannot merge a change
request it opened.
The Chat's access to connectors follows the connectors grant of an agent named harness-main when the manifest
declares one, and the grant of default_agent otherwise.
connectors
A connector is a tool agents can call. The definition lives in the file. The credential lives in Auto Lab and never in git. See Connectors.
| Field | Default | Notes |
|---|---|---|
slug | required | Lowercase, unique among connectors. kortix_slack, kortix_teams and kortix_email are reserved for channels. |
provider | required | See the next table. |
name | the slug | Display name. |
enabled | true | |
authorization_strategy | project | project uses the goal's shared connection. user uses only the connection of the person the agent acts for. |
sensitive | false | true makes every call ask first, reads included, unless a rule allows it. |
policies | Rules for this connector's tools. match is a pattern over tool names, action is always_run, require_approval or block. | |
auth | How to send the credential: type (bearer, basic, api_key, custom, oauth1, hmac, aws_sigv4, mtls or none), in (header, query or cookie) and name for api_key and custom. | |
headers | Fixed request headers, up to 32. They are plain text in git, so never put a credential here. |
| Provider | Needs | Notes |
|---|---|---|
composio | app | An app from the connector catalogue, such as gmail. |
pipedream | app | |
mcp | url | transport is http (default) or sse. |
openapi | spec | A URL or a path in the repository. |
postman | spec | A collection URL or path. |
graphql | endpoint | spec is optional. |
http | base_url | spec is optional. |
channel | platform | slack, teams or email. Auto Lab writes these when you connect a channel. |
Tool rules
Patterns use * as a wildcard and ignore case. A connector's own policies match its tool names. The top-level
policies match <connector>.<tool> and apply to every connector. Auto Lab decides each call in this order, and
the first answer wins:
- Top-level
policies, top to bottom. - The connector's
policies. sensitive: trueon the connector: ask first.policy.default_mode:allow_allruns the call.riskruns reads and asks before writes and deletes.
The Delegation dial writes these keys for you. Asks before acting adds a first rule that asks before every
call and sets risk. Acts on its own removes that rule and sets allow_all. Acts within your rules puts
your own rules and mode back. See Approvals and autonomy.
triggers
A trigger runs the agent on a schedule or when a signed HTTP request arrives. See Plans, schedules and triggers.
| Field | Default | Notes |
|---|---|---|
slug | required | Lowercase, unique among triggers. |
type | required | cron, webhook or monitor. |
prompt | required | The message the agent receives. {{ … }} placeholders are filled from the payload. |
name | the slug | Display name. |
agent | default_agent | Must name a declared agent. Leave it out rather than writing default. |
enabled | true | false keeps the trigger without firing it. |
model | chosen when it fires | Pins a model, as provider/model. See Models. |
session_mode | fresh | Which session a fire uses. See below. |
filter | Payload paths and the values they must equal, such as body.type: lead. A delivery that does not match is accepted and ignored. |
Cron. Set cron to a six-field expression (second, minute, hour, day of month, month, day of week), or
run_at to one ISO-8601 time for a single run. timezone is an IANA name and defaults to UTC.
Webhook. The URL is https://api.auto-lab.ai/v1/webhooks/projects/<goal-id>/<slug>. secret_env names the
secret that holds the signing key. Sign the raw body with HMAC-SHA256 and send X-Kortix-Signature: sha256=<hex>;
GitHub's X-Hub-Signature-256 also works. A sender that cannot sign can send the key itself in X-Kortix-Token or
Authorization. The prompt can use {{ body.<path> }}, {{ headers.user_agent }}, {{ trigger.slug }} and
{{ fired_at }}. The secret must be delivered to connectors only, never to a machine:
kortix secrets delivery FORM_WEBHOOK_SECRET none --consumer connector
Monitor. An always-on command from the repository whose output lines are events. Monitors are experimental and need the Monitors flag in Feature flags, which is off by default.
Session mode. fresh starts a new session on every fire. reuse sends each fire to the session this trigger
started last, so context builds up; monitors use it by default. pinned uses the session in session_id. A
session_key such as {{ body.chat_id }} keeps one session per value; an empty key falls back to fresh.
On a goal with the Chat, a webhook or monitor fire does not start a session. It wakes the Chat with the trigger's prompt and a summary of the payload. If the agent was not waiting for that event, the turn can only read, not act. Cron triggers still start a session, and those sessions count against Settings › Autonomy limits (New sessions per day, Sandbox minutes per day). The plan's routine and check-ups are not manifest triggers on these goals. They are the agent's own schedules in Brain › Schedules.
env
env.required and env.optional list names only. Values are secrets and never go in the
file. Settings › Secrets lists the declared names so people know what to fill in; a missing required value
does not stop a session. Names use letters, digits and _, do not start with a digit, are stored in upper case and
fit in 64 characters. KORTIX_ names are reserved. An agent receives a secret only through its secrets grant.
sandbox
sandbox.templates is a list of machine images. Each entry takes these fields:
| Field | Notes |
|---|---|
slug | Required and unique. Up to 64 characters in practice. default is reserved. |
name | Display name. Defaults to the slug. |
dockerfile | A path inside the repository. Set this or image, not both. |
image | A public image with a pinned tag or digest, such as python:3.12-slim. latest gets a warning. |
entrypoint | Leave it unset. |
cpu, memory, disk | 1 to 32 vCPUs, 1 to 128 GiB of memory, 1 to 500 GiB of disk. |
A value below the minimum is an error. A value above the maximum gets a warning and is capped. GPUs are not
supported. sandbox.default must name a template declared here, or default for the Auto Lab image. An agent's
own sandbox key wins over it. For Dockerfile rules and rebuilds, see
Sandbox templates.
When changes take effect
- A change in a session or a clone takes effect once its change request merges into the default branch. New sessions start from that branch, and the Chat picks the change up within about a minute.
- A change saved in the UI (a trigger, a connector, a tool rule, the Delegation dial) is committed to the
default branch at once. Auto Lab rewrites the file from its parsed contents when it saves, so comments are
dropped and
kortix_versionmoves to the top. - A webhook trigger answers
404until its entry is on the default branch and enabled. - A sandbox change builds a new image. Running sessions keep the machine they started on.
- A secret value is read when a machine starts, so it applies to the next session.