— Concepts

The mental model behind A3.

A3 splits automation into the parts a model is good at — observation, decision, repair — and the parts code is good at — selectors, control flow, schemas. Each concept below corresponds to a real package in the monorepo.

01 · WORKFLOW

A workflow is the contract.

A workflow declares the high-level objective — scrape hotel rates, file an expense — and binds it to a typed input schema, a typed output schema, and a directory of stage modules. You author this file; A3 uses it as the contract for learning and execution.

title
Human-readable label shown in the UI and CLI.
rootDir
Project source root, e.g. 'src'. Used to resolve module paths.
sitesDir
Directory whose immediate children are per-site folders of *.stage.ts.
inputsSchema
Zod schema defining what callers must provide.
outputsSchema
Schema satisfied by stages marked success: true.
agents
Agent definitions registered with the workflow scope.
projectLocations
Folders the agent may read or write — objectives, schemas, skills.
setup / teardown
Optional hooks; useful for seeding ctx.inputs in dev runs.
Mission Learning
objectives/mission.md
Objective: scrape nightly room rates for a target hotel from public booking pages. 
  
For each date in the requested stay window, capture normalized room entries including room name, occupancy constraints, board type, cancellation policy, currency, tax and fee breakdown, and final payable price. 

Prefer API or network responses when available, and fall back to stable DOM extraction when required. Ensure outputs validate against the workflow schema and preserve source metadata needed for downstream diffing and replay verification.
Workflow: Scrape Rates Configuration
scrape-rates.workflow.ts
export default defineWorkflow({
  title: 'Scrape Rates',
  description: 'Scrape rates from a website',
  inputsSchema: ScrapeRatesInputSchema,
  outputsSchema: ScrapeRatesOutputSchema,
  rootDir: 'src',
  sitesDir: import.meta.dirname + '/sites',
  missionFile: import.meta.dirname + '/objectives/mission.md',
  agents: [TestAgent],
  projectLocations: [
    { dir: objectivesDir, writable: true, patterns: ['*.md'] },
    { dir: schemaDir, writable: false },
  ],
  setup: async (ctx) => { ctx.inputs = sampleInputs; },
});
02 · STAGE

A stage owns one logical state.

Each stage corresponds to a single recognisable state of the target service — a landing page, a results table, a confirmation modal. Its match() answers “is this me?” with no side effects. Its run() takes the actions that move the automation to the next state. During learning, A3 creates and updates these stage files.

match(ctx)
Pure boolean check. No clicks, no fetches.
run(ctx)
Performs interactions, populates ctx.outputs.
success
true marks the stage as a terminal-success outcome.
overlay
Marks dialogs/popups; matched ahead of normal stages.
repeatable
Allow the stage to match more than once per run.
Stage: Extract rooms Learning
sites/be-synxis-com/extract-rates.stage.ts
import { defineStage } from '@athree/runner';

export default defineStage({
  title: 'Extract Rooms and Rates',
  description: 'Prefer availability JSON; scrape the grid if it is missing',
  success: true,
  match: async (ctx) => {
    const path = new URL(ctx.page.url()).pathname;
    if (!path.includes('/hotel/')) return false;
    return await ctx.page.locator('[data-rates-grid]').first().isVisible();
  },
  run: async (ctx) => {
    const intercepted = ctx.networkResources.find((r) =>
      r.request.url.includes('getProductAvailability'));
    if (intercepted?.response?.responseBody) {
      const data = JSON.parse(intercepted.response.responseBody);
      ctx.outputs.hotelId = ctx.inputs.search.hotelId;
      ctx.outputs.results = [{
        search: ctx.inputs.search,
        rooms: normalizeRooms(data),
      }];
      return;
    }
    // No JSON: walk the rates table and normalize rows.
    ctx.outputs.results = await extractFromRateTable(ctx.page, ctx.inputs.search);
  },
});
03 · SITE

The site filters the candidate pool.

Stages live under sites/<hostname>/. After your main.stage navigates, the runner reads new URL(ctx.page.url()).hostname, slugifies it (dots → dashes), and uses that as ctx.site. From then on only stages in that folder (plus the default/ globals) are candidates — no cross-contamination, and you scale to many services by adding folders.

FS routing
sites/be-synxis-com/ → site be-synxis-com. No registry.
default/
Folder of stages that always match, regardless of site.
Add a site
Add a folder. The next learning run populates it.
Site directories Configuration
sites/
├── default/
│   └── main.stage.ts          # global, navigates and sets ctx.site
├── be-synxis-com/
│   ├── main.stage.ts
│   ├── extract-rates.stage.ts
│   ├── inputs.yaml
│   └── playbook.md
├── asp-hotel-story-ne-jp/
│   ├── main.stage.ts
│   ├── inputs.yaml
│   └── playbook.md
└── www-rufinohotelpetitmendoza-com/
    └── 
04 · AGENT

An agent handles what code can't.

Some tasks resist deterministic code: extracting unstructured prose, classifying an unfamiliar layout, summarising error states. Wrap those in an agent — a system prompt, optional input/output schemas, optional tools. You author these agents; they return validated structured payloads or fail loudly.

id
Stable identifier; how stages and other agents reference it.
title / description / icon
UI metadata. icon takes a Font Awesome class.
systemPrompt
Function returning { content }. Composed at agent invocation time.
tools
Array of tool classes. Built-ins: EvaluateTool, AskUserQuestionTool, FailTool.
Agent: Test Configuration
TestAgent.ts
import {
  AskUserQuestionTool, defineAgent,
  EvaluateTool, FailTool,
} from '@athree/runner';

export const TestAgent = defineAgent({
  id: 'test_agent',
  title: 'Test Agent',
  description: 'A test agent',
  icon: 'fas fa-bug',
  systemPrompt: () => ({
    content: `You assist the user with debugging…`,
  }),
  tools: [EvaluateTool, AskUserQuestionTool, FailTool],
});
05 · TOOL

Tools are typed functions.

A tool is the smallest unit an agent can call. Strict input/output schemas, an execute function, optional dependency-injected services. Define them once and reuse across agents.

Tool: parse-date Configuration
tools/parse-date.tool.ts
export default defineTool({
  description: 'Parses date in given format → ISO 8601',
  inputSchema: z.object({
    text: z.string(),
    format: z.string(),
  }),
  outputSchema: z.string(),
  execute: async ({ text, format }, ctx) => {
    return parseToIso(text, format);
  },
});
06 · LEARNING

Learning grows your stages from your mission and agent.

Learning turns an objective and observed site behaviour into stage modules and a playbook. In practice, the result is what matters: A3 writes and updates stage files plus playbook.md in your project.

Input
Your mission and workflow-defined agent behavior.
Output
New or updated *.stage.ts files under sites/<site>/.
Knowledge
A maintained playbook.md describing known flow states and variants.
orchestrator decisionstructured
Adelegate(targetAgentId, input)subagent
Brequest_user(question)pause
Cfinish(outcome, summary)terminate
Playbacks drive routing. NoStagesMatchError and MultipleStagesMatchError are first-class signals. The orchestrator routes them to the right subagent rather than re-deriving intent from chat.
07 · PLAYBOOK

Site truth, in markdown.

Each site directory holds a playbook.md next to its stages. Sections are stable so the agent can patch them surgically. Playbooks describe entry points, flow states, variants and known risks — the things stages must not re-derive on every run.

## Objective
What the site is being automated for.
## Entry points
Where the flow can begin.
## Flow states
The recognisable states the agent has mapped.
## Variants
Alternate paths and A/B layouts the runner must tolerate.
## Known risks
Anti-bot, rate limits, brittle selectors.
## Changelog
Append-only history of meaningful site changes.
Playbook Learning
sites/…/playbook.md
# Booking.com — Search rates

## Objective
Retrieve nightly rates for a given hotel, date range
and party composition.

## Entry points
- Direct hotel URL with checkin/checkout query params
- Homepage → search → first result

## Flow states
1. cookie-banner (overlay)
2. hotel-landing
3. rates-table (final, success)

## Variants
- A/B: occupancy modal appears only when guests > 4
- Mobile layout served when UA is iOS/Android

## Known risks
- 30s captcha challenge after ~12 navigations
- Currency switch requires explicit ctx.input
08 · ARCHITECTURE

Strict layers, strict topology.

The monorepo is layered: protocol packages own contracts, runtime packages own behaviour, and the frontend consumes typed RPC. Imports follow a topology — protos depend on nothing runtime, helpers depend on protos, runtime depends on both.

Protocol
@athree/runner-proto @athree/manager-proto
Shared
@athree/helpers @athree/module-loader
Runtime
@athree/runner @athree/manager @athree/runner-extensions
Surface
@athree/cli @athree/frontend