Skip to content
Registry

tabstack

v0.2.0

Browsing infrastructure for AI agents on the iii bus — tabstack::* functions turn any URL into clean markdown or structured JSON, and run cited web research through the hosted Tabstack API.

license: Apache-2.0webscrapingextractionmarkdownresearchbrowseragentsurl

Install

bash
iii trigger compose::add worker=tabstack

Run in your project with the iii engine running. Exact versions are immutable and digest-pinned.

Tabstack

Tabstack reads the sources, checks what is missing, reconciles conflicting information, and returns a cited answer in one call. The same API also returns clean text, matching JSON, or a completed public web task.

Backed by the hosted Tabstack API, built by Mozilla. You bring a Tabstack API key; usage is billed to your own account. Full endpoint semantics live in the Tabstack API reference.

Install

iii trigger compose::add worker=tabstack

iii trigger compose::add resolves the worker and its dependencies, writes exact declarations to worker-compose.yaml, and reconciles the Compose project.

Configuration

The worker needs a Tabstack API key. Sign up free — the trial needs no card.

It registers a schema and seed in the configuration worker on boot, so the key can be set from the Console or with configuration::set:

tabstack:
  api_key: your-api-key                 # required; no default
  base_url: https://api.tabstack.ai/v1  # override only for testing

If no key is stored, the worker falls back to the TABSTACK_API_KEY environment variable. With neither set, every call fails with tabstack_not_configured.

Note the configuration entry is namespaced: in the default namespace the id is default-tabstack, not tabstack.

Quickstart

Read a page as markdown:

iii trigger tabstack::extract-markdown url=https://example.com
{
  "ok": true,
  "url": "https://example.com",
  "content": "---\ntitle: Example Domain\n---\n\nThis domain is for use in documentation examples..."
}

Pull structured data out of a page by describing the shape you want:

iii trigger tabstack::extract-json url=https://news.ycombinator.com \
  json_schema='{"type":"object","properties":{"stories":{"type":"array","items":{"type":"object","properties":{"rank":{"type":"integer"},"title":{"type":"string"},"points":{"type":"integer"}}},"maxItems":5}}}'
{
  "ok": true,
  "data": {
    "stories": [
      { "rank": 1, "title": "Web Search API", "points": 318 },
      { "rank": 2, "title": "The future of independence is interdependence", "points": 41 }
    ]
  }
}

From another worker:

import { registerWorker } from 'iii-sdk';

const iii = registerWorker('ws://localhost:49134', { workerName: 'my-worker' });

const result = await iii.trigger({
  function_id: 'tabstack::extract-markdown',
  payload: { url: 'https://example.com' },
  timeoutMs: 60_000,
});

Raise the call timeout. iii's default is 30 seconds. extract-markdown and extract-json usually return well inside that, but effort: "max" renders the page in a full browser and is documented at up to 60 seconds, and automate runs for minutes. A caller on the default gives up before the work finishes.

From the CLI it is a flag, not a key/value pair — --timeout-ms goes before the function name. timeout_ms=… as a pair is passed through as payload and silently has no effect:

iii trigger --timeout-ms 300000 tabstack::automate task='...' url='...'

From another worker it is timeoutMs on the trigger call.

The functions

Function Takes Returns
tabstack::extract-markdown url the page as markdown in content
tabstack::extract-json url, json_schema matching object in data
tabstack::generate-json url, json_schema, instructions matching object in data
tabstack::research query a cited markdown answer in report
tabstack::automate task the agent's final answer in result

See the Tabstack API reference for more details about each function.

Researching rather than reading

research is the one function that does not take a url. Give it a question and it searches, reads and synthesises an answer with inline citations:

iii trigger --timeout-ms 180000 tabstack::research query='What is the iii.dev worker registry?'
{
  "ok": true,
  "report": "The iii.dev worker registry is a central online repository located at `workers.iii.dev` ... [1][2]",
  "metadata": {
    "citedPages": [{ "id": "5YI5Rc", "title": "iii worker - iii.dev", "url": "https://workers.iii.dev/" }],
    "mode": "fast",
    "totalPagesAnalyzed": 3
  }
}

Completing a task in a browser

automate drives a real browser from a plain-language task: navigate, read, fill forms, work through multi-step flows.

iii trigger --timeout-ms 300000 tabstack::automate \
  task='Fill in the contact form with the supplied details' \
  url=https://example.com/contact \
  data='{"name":"Alex Johnson","email":"alex@example.com"}' \
  guardrails='fill the fields only, do not submit' \
  trusted_hostnames='["example.com"]'
{ "ok": true, "result": "The name and email fields have been populated. The form was not submitted." }

Put every value the task needs in data. A value that is missing may be filled with something the agent invented rather than reported back, so do not rely on it to ask you.

guardrails is worth using. It constrains what the agent may do — "browse and extract only, do not submit any form" is a sensible default for anything read-only.

trusted_hostnames lifts the action firewall for fills and submissions on the hosts you list. Page content on those hosts can steer the agent, so list only sites you would trust with whatever is in data.

Runs take minutes. The timeout is a CLI flag, not a payload key — see above.

Choosing between extract-json and generate-json

extract-json takes a schema alone and is the cheaper of the two. Reach for it whenever the schema says enough about what you want.

generate-json additionally takes natural-language instructions, for when the schema alone cannot express how values should be derived — ranking, classifying, or comparing across a whole page. "Pick the dominant theme across these stories" needs an instruction; a bare schema cannot say it.

Reading the result

Every function returns an envelope. On success, ok is true and the result sits alongside it. When Tabstack returns an error, ok is false and the call still succeeds at the iii level:

{ "ok": false, "error": "tabstack_invalid_request", "message": "invalid URL" }
error Meaning
tabstack_auth_failed key missing, wrong or revoked
tabstack_out_of_credits account balance exhausted
tabstack_rate_limited too many requests; back off and retry
tabstack_not_found endpoint not found
tabstack_invalid_request bad URL, malformed schema, or similar
tabstack_upstream_error Tabstack-side failure; safe to retry
tabstack_automation_failed the browser task could not be completed; message says why
tabstack_incomplete_stream the response ended before finishing (research, automate)

Failures are returned rather than thrown so callers can branch on error. The Node SDK collapses every thrown handler error to invocation_failed, which would make these indistinguishable.

Two cases are thrown, because no useful result exists: tabstack_not_configured when no API key is available, and tabstack_unreachable / tabstack_timeout when the request never reached Tabstack at all.

These codes map onto the HTTP statuses in the Tabstack API reference, which is the authority on what each endpoint can return.

Search the registry

Find workers, kits and executables, or jump to a page.