<https://docs.linkeddatahub.com/reference/assistant/>
        a       <https://w3id.org/atomgraph/linkeddatahub/document-hierarchy#Item>;
        <http://www.w3.org/1999/02/22-rdf-syntax-ns#_1>
                <https://docs.linkeddatahub.com/reference/assistant/#content>;
        <http://purl.org/dc/terms/created>
                "2026-10-16T23:54:43.233Z"^^<http://www.w3.org/2001/XMLSchema#dateTime>;
        <http://purl.org/dc/terms/creator>
                <https://admin.linkeddatahub.com/acl/agents/5ba81960-d511-4a3b-8b9a-cb5bb278dd95/#this>;
        <http://purl.org/dc/terms/description>
                "The built-in assistant: how questions become Web-Algebra plans, how it is set up, and how conversations are stored";
        <http://purl.org/dc/terms/modified>
                "2026-10-17T19:58:45.278Z"^^<http://www.w3.org/2001/XMLSchema#dateTime>;
        <http://purl.org/dc/terms/title>
                "Assistant";
        <http://rdfs.org/sioc/ns#has_container>
                <https://docs.linkeddatahub.com/reference/>;
        <http://www.w3.org/ns/auth/acl#owner>
                <https://admin.linkeddatahub.com/acl/agents/5ba81960-d511-4a3b-8b9a-cb5bb278dd95/#this> .

<https://docs.linkeddatahub.com/reference/assistant/#content>
        a       <https://w3id.org/atomgraph/linkeddatahub#XHTML>;
        <http://www.w3.org/1999/02/22-rdf-syntax-ns#value>
                "<div xmlns=\"http://www.w3.org/1999/xhtml\">\n    <p class=\"ldh-body-lg\">The built-in assistant: how questions become Web-Algebra plans, how it is set up, and how conversations are stored</p>\n    <p>The assistant turns a question into a <a href=\"https://github.com/AtomGraph/Web-Algebra\" target=\"_blank\">Web-Algebra</a> plan, shows the plan, and runs\n        it when the reader says so. It runs in the browser as part of the client stylesheet and talks to a <samp>web-algebra</samp> service beside the platform,\n        which is optional and off by default — see <a href=\"#setup\">Setup</a>.\n        The <a href=\"../../user-guide/use-the-assistant/\">user guide</a> covers using it. To work with LinkedDataHub from an agent of your own instead, see\n        <a href=\"../../get-started/agents/\">Connect an agent</a>.</p>\n    <div>\n        <h2 id=\"architecture\">How it works</h2>\n        <ol>\n            <li>The client sends the question to <samp>/webalgebra/plans</samp> on the dataspace's own origin, with the current document, the dataspace's\n                SPARQL endpoint and ontology, and the last three turns of the conversation that ran, each with its plan, its outcome and up to a hundred\n                of the rows it returned.</li>\n            <li>nginx forwards <samp>/webalgebra</samp> to the <samp>web-algebra</samp> service together with the client certificate of the connection. A\n                request without a client certificate is answered with <samp>401</samp>, since the service has nobody to act for.</li>\n            <li>The service asks the model for a plan, checks it against its executor, and answers with the plan, a one-sentence summary and the operations\n                it uses. The client shows them as a card.</li>\n            <li>On Execute, the client submits the plan to <samp>/webalgebra</samp>. The service answers <samp>202</samp> with an execution id, and the\n                client polls <samp>/webalgebra/{id}/result</samp> once a second, showing each step as the executor reports it.</li>\n            <li>When the plan has run, the client sends the result to <samp>/webalgebra/answers</samp> for a sentence that sums it up, then\n                <a href=\"#data-model\">stores the turn</a> in the conversation's document and reloads the documents the plan wrote.</li>\n        </ol>\n        <p>A plan that only reads may run as soon as it arrives. One that writes always waits for the reader to press Execute.</p>\n    </div>\n    <div>\n        <h2 id=\"access-control\">Access control</h2>\n        <p>The service runs every plan as the reader who asked. It presents the secretary's WebID certificate to the platform, with an\n            <code>On-Behalf-Of</code> header naming the reader's WebID. The platform honours it because every agent's WebID document that the platform creates,\n            at <a href=\"../../get-started/get-an-account/\">sign-up</a> or at the first Google or ORCID login, states that the secretary delegates that agent\n            (<code>&lt;secretary&gt; acl:delegates &lt;agent&gt;</code>). Plans are therefore subject to the reader's own\n            <a href=\"../administration/acl/\">authorizations</a>: a plan can read and write what the reader can, and nothing else.</p>\n        <p>nginx clears any <code>On-Behalf-Of</code> and <code>Client-Cert</code> headers arriving from outside, so identity comes only from the TLS\n            connection. The <samp>web-algebra</samp> service is reachable only through nginx.</p>\n        <p>On a deployment that <a href=\"#setup\">enables the assistant</a>, the <span class=\"ac-btn in-neutral ap-outline sz-md\">Assistant</span> button\n            appears for a signed-in agent with append access to the document, and a conversation accepts new questions under the same condition.</p>\n    </div>\n    <div>\n        <h2 id=\"setup\">Setup</h2>\n        <div class=\"ac-alert va-warning\" role=\"status\">\n            <span class=\"ac-alert-ic\"><span aria-hidden=\"true\" class=\"msi outline\">warning</span></span>\n            <div class=\"ac-alert-body\">\n                <p class=\"ac-alert-text\">The <samp>web-algebra</samp> service runs the <samp>ghcr.io/atomgraph/webalgebra-server-ldh</samp> image, which is proprietary: AtomGraph publishes the image but not its source, and LinkedDataHub's license does not cover it. The assistant is therefore opt-in.</p>\n            </div>\n        </div>\n        <p><samp>docker-compose.yml</samp> declares the <samp>web-algebra</samp> service under the <samp>assistant</samp> Compose\n            <a href=\"https://docs.docker.com/compose/how-tos/profiles/\" target=\"_blank\">profile</a>, so it does not start unless the profile is active, and\n            the Assistant button stays hidden until the platform is told that the service runs. Writing plans from questions also needs an\n            <a href=\"https://platform.openai.com/api-keys\" target=\"_blank\">OpenAI API key</a>:</p>\n        <ol>\n            <li>Enable the profile in <samp>.env</samp>:\n                <pre>COMPOSE_PROFILES=assistant</pre>\n                The profile starts the service, and the platform reads the same variable: it then publishes <code>lds:assistant true</code> for every\n                dataspace in its <samp>/context</samp>, which is what the button is shown on. Set it in <samp>.env</samp> rather than with\n                <samp>docker compose --profile</samp>, which the platform does not see.</li>\n            <li>Put the key in <samp>secrets/openai_api_key.txt</samp></li>\n            <li>Declare the secret and hand it to the service in <samp>docker-compose.override.yml</samp>:\n                <pre>secrets:\n  openai_api_key:\n    file: ./secrets/openai_api_key.txt\nservices:\n  web-algebra:\n    environment:\n      - OPENAI_API_KEY_FILE=/run/secrets/openai_api_key\n    secrets:\n      - secretary_cert_password\n      - openai_api_key</pre>\n            </li>\n            <li>Recreate the services: <samp>make up</samp></li>\n        </ol>\n        <p>Without a key the service still runs plans, but declines to write them: a question is answered with a message saying so, shown in the card.\n            The sentence that sums up a result also comes from the model; without it, the card shows the rows alone.</p>\n        <p>The service's variables are listed in the <a href=\"../configuration/#web-algebra\">configuration reference</a>.</p>\n        <div>\n            <h3 id=\"without-service\">Running without the service</h3>\n            <p>Without the profile, the rest of the stack runs as usual: nginx looks the service up per request instead of at startup, and answers\n                <samp>/webalgebra</samp> with <samp>502</samp>. Conversations stored while the assistant was enabled are still drawn from the store, but a\n                new question in them fails with an error in its card.</p>\n        </div>\n    </div>\n    <div>\n        <h2 id=\"data-model\">Data model</h2>\n        <p>A conversation is an <code>ldh:Chat</code> resource, placed in a document by an <a href=\"../data-model/blocks/#object-blocks\">object block</a>\n            like a view or a chart. Its turns are <code>ldh:ChatTurn</code> resources in the same document, and the chat's <code>rdf:_1</code>,\n            <code>rdf:_2</code>, … members in the order they were asked. The first question writes the chat, the object block and the document's next\n            <code>rdf:_N</code> for the block in one request; the chat and its block share a fragment suffix, and the chat's title is the first question, cut near\n            80 characters. Each turn is written when its answer has landed, or has failed to.</p>\n        <table>\n            <caption class=\"ac-vh\">Properties of an assistant turn</caption>\n            <thead>\n                <tr>\n                    <th scope=\"col\">Property</th>\n                    <th scope=\"col\">Value</th>\n                </tr>\n            </thead>\n            <tbody>\n                <tr>\n                    <th scope=\"row\"><code>ldh:question</code></th>\n                    <td>What the reader asked</td>\n                </tr>\n                <tr>\n                    <th scope=\"row\"><code>ldh:answer</code></th>\n                    <td>What the plan did, read by the model, as an <code>rdf:XMLLiteral</code>: an XHTML <code>div</code> of paragraphs, lists, emphasis, code and links to what the plan returned, when the model gave one. Other markup is dropped, and so are links that are not http(s). Turns stored before answers were XHTML hold a plain string</td>\n                </tr>\n                <tr>\n                    <th scope=\"row\"><code>ldh:plan</code></th>\n                    <td>The Web-Algebra plan as an <code>rdf:XMLLiteral</code>: its summary, its operations and the plan itself</td>\n                </tr>\n                <tr>\n                    <th scope=\"row\"><code>ldh:execution</code></th>\n                    <td>What running the plan reported, as an <code>rdf:XMLLiteral</code>: the steps, the documents written, and the first hundred rows or resources of the result</td>\n                </tr>\n                <tr>\n                    <th scope=\"row\"><code>ldh:outcome</code></th>\n                    <td>How the turn went, in one line: rows or resources returned, documents written, no results, the failure's message, or <samp>declined</samp> when the service answered in words instead of with a plan</td>\n                </tr>\n                <tr>\n                    <th scope=\"row\"><code>dct:created</code></th>\n                    <td>When the turn was stored</td>\n                </tr>\n            </tbody>\n        </table>\n        <p>A stored conversation is drawn from these resources when the document is rendered again, so it shows the result as it was when the turn ran;\n            nothing is run again. Since the chat is an ordinary resource, its object block can also be placed in another document, and questions asked\n            there are stored in the chat's own document.</p>\n        <pre>@prefix ldh: &lt;https://w3id.org/atomgraph/linkeddatahub#&gt; .\n@prefix rdf: &lt;http://www.w3.org/1999/02/22-rdf-syntax-ns#&gt; .\n@prefix dct: &lt;http://purl.org/dc/terms/&gt; .\n@prefix xsd: &lt;http://www.w3.org/2001/XMLSchema#&gt; .\n\n&lt;https://localhost:4443/customers/&gt; rdf:_3 &lt;https://localhost:4443/customers/#block-91c0…&gt; .\n\n&lt;https://localhost:4443/customers/#block-91c0…&gt; a ldh:Object ;\n    rdf:value &lt;https://localhost:4443/customers/#chat-91c0…&gt; .\n\n&lt;https://localhost:4443/customers/#chat-91c0…&gt; a ldh:Chat ;\n    dct:title \"Which five customers ordered the most in 1997?\" ;\n    rdf:_1 &lt;https://localhost:4443/customers/#turn-5d7e…&gt; .\n\n&lt;https://localhost:4443/customers/#turn-5d7e…&gt; a ldh:ChatTurn ;\n    ldh:question \"Which five customers ordered the most in 1997?\" ;\n    ldh:answer \"&lt;div xmlns=\"http://www.w3.org/1999/xhtml\"&gt;&lt;p&gt;The five customers with the most orders in 1997 were:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;a href=\"…\"&gt;Save-a-lot Markets&lt;/a&gt;: 17 orders&lt;/li&gt;…&lt;/ol&gt;&lt;/div&gt;\"^^rdf:XMLLiteral ;\n    ldh:plan \"&lt;wa:plan …&gt;…&lt;/wa:plan&gt;\"^^rdf:XMLLiteral ;\n    ldh:execution \"&lt;wa:execution …&gt;…&lt;/wa:execution&gt;\"^^rdf:XMLLiteral ;\n    ldh:outcome \"5 rows\" ;\n    dct:created \"2026-10-17T09:12:44Z\"^^xsd:dateTime .</pre>\n    </div>\n</div>"^^<http://www.w3.org/1999/02/22-rdf-syntax-ns#XMLLiteral> .
