OpenClaw-Powered Queryless for PortalJS

Technical overview of how the OpenClaw-powered implementation of Queryless for PortalJS portals works.

This is a transitional implementation and is meant to be replaced in the future by STANDALONE.md.

Why we prototyped with OpenClaw

We used OpenClaw for the first implementation because it made the prototype fast to build and fast to iterate on.

  • Quick development - Much of the implementation could be done by interacting with OpenClaw directly
  • Fast iteration - Agent behavior, prompts, and supporting scripts could be adjusted quickly
  • No CI/CD overhead - Nothing needed to be deployed as a standalone production service during the prototyping phase

High-level architecture

The setup has three main parts:

  • OpenClaw running in a VM - This is where the Queryless agent executes
  • A QuerylessAI-PortalJS agent defined inside OpenClaw - This contains the agent's instructions and tools
  • A PortalJS integration layer - This sends portal context and user messages to the OpenClaw API, and renders the chat on the PortalJS frontend

OpenClaw runs in a VM with access to the full machine environment. In practice, that means it can perform normal computer operations such as creating files, running commands, and working with downloaded data locally.

What the OpenClaw agent consists of

In this setup, an OpenClaw agent is essentially a combination of context files and executable tools.

Context files

These are markdown files that define how the agent behaves. For example:

  • IDENTITY.md - Who the agent is
  • SOUL.md - How it responds
  • SKILLS.md - What it can do, such as querying datasets or creating visualization specs
  • Other supporting markdown files as needed

These files compose the system prompt that is sent to the LLM.

Scripts and tools

These are scripts the agent can use while responding, for example:

  • package_search
  • download_data_file
  • query_data

How PortalJS integrates with it

OpenClaw exposes an API, and the PortalJS integration calls that API.

For each conversation, PortalJS sends both the user message and some predefined context about the portal the user is currently browsing.

That context includes:

  • CKAN API URL - The agent uses this to call the CKAN API behind the PortalJS portal, for example package_search, package_show, and organization_list
  • Portal routing patterns - For example, dataset search page -> /datasets, dataset details page -> /@{org-name}/@{dataset-name}. The agent uses this to generate relative links that point users to the PortalJS frontend rather than the CKAN UI
  • Current page context - The agent uses this to infer likely user intent and understand what the user is already looking at. For example, if the user is on a dataset page and asks "show me a sample of this data", the agent does not need to ask which dataset they mean

The entrypoint for the integration code is https://github.com/datopian/portaljs-frontend-starter/blob/main/components/queryless/QuerylessAssistant.tsx

Session management

On the PortalJS side, each conversation gets a unique conversation ID. That ID is sent to the OpenClaw API and used for session management.

If the user refreshes the window or clicks the trash icon, PortalJS generates a new conversation ID. That starts a brand new session on the OpenClaw side.

How data querying works

The current PortalJS integration does not use Datastore.

When a user asks for an answer, visualization, or insight about a specific resource file, the OpenClaw agent:

  1. Downloads the file locally
  2. Generates a SQL query for the task
  3. Uses DuckDB to run that query against the local file

This is the current mechanism for file-level analysis in the prototype.

Guardrails

Guardrails are currently implemented through the system prompt.

For example, some instructions are marked as critical so the agent does not handle requests unrelated to the portal or its data.

Built with LogoFlowershow