An assistant: a request in words becomes a Web-Algebra plan, in a chat block of its own - #407
Merged
Merged
Conversation
…ore it runs The drawer on the right edge of every page for a signed-in reader sends a question in natural language to the web-algebra sidecar's plan service, with the document the reader is on, its dataspace's SPARQL endpoint and the earlier turns. The plan comes back as its summary, the operations it invokes and the XML itself, and runs only on Execute (or at once with "Execute by default"); the steps report as the executor does, the result set or the documents written are shown, and the page catches up. Each card keeps its plan and, once run, what it returned, so a follow-up that refers to "them" is planned on that result as a VALUES block rather than on a new query. A Clear button empties the log and both stores. LDH's operation family is addressed as `waldh:` elements. The stack gains the web-algebra service and nginx's reserved /webalgebra path with the caller's certificate; OPENAI_MODEL picks the planner's model. The chat section of ldh.css parks the core Drawer under the sticky header. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…raph is empty A client writing a query against a dataspace's endpoint has to know that every document is a named graph and a pattern outside GRAPH matches nothing; the assistant's SPARQLString reads an endpoint's AGENTS.md as service documentation and was answering "no results" for want of it. The repository's guide is shipped in the WAR from where it lives, mapped to the default servlet beside robots.txt and sitemap.xml so it is public and never enters the dispatcher, served as text/markdown, and gains the named-graph paragraph in its Querying section. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…t they return The drawer moves inside #tab-body, beside the panes and under the dataspace tab bar: the header and the tab bar keep the full width, the drawer starts where the panel does, and its head takes the action bar's height so the two seams are one line. An open drawer insets the panes and the footer rather than the frame. The create dock's full-bleed margin and padding, computed on the viewport, slid its buttons under the drawer; they now share --ldh-pane-w, the panes' width, which the open drawer narrows. A result set's rows get their labels: the distinct URIs are looked up with the two metadata queries a view block runs, against the endpoint the plan queried, and the rows are drawn again with the metadata tunnelled to the link leaf when the labels land. The UI spec measures the panes and the seam, and expects the written container by its title. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…e OAuth ones The base compose file no longer binds secrets/openai_api_key.txt, so a stack without a key - the test workflows, a deployment that only runs plans - comes up; a deployment that writes plans declares the openai_api_key secret and OPENAI_API_KEY_FILE in its override, which is where the dev stack now has them. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…nt guide says a join crosses documents The dataspace's ontology, which every document advertises with Link rel=lds:ontology, is the vocabulary its domain data is described in: labels, domains and ranges the planner's induced schema lacks, and none of the documents' plumbing the induced one picks up. The client now keeps the Link target the way it keeps the endpoint, lds:ontology() reads it, and the question goes to the plan service with it, so a SPARQLString against this dataspace is written from GET of the ontology rather than from ExtractOntology (REST-VKG #26). The guide the query writer reads from /AGENTS.md gains the two rules the Northwind evaluation showed the model breaking: a resource is described in the document about it, so a pattern that follows a link to another resource needs a GRAPH of its own for each; and a property is used on instances of its domain, so what the ontology declares on Person is reached through the corporation's employee, not written on the corporation. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…depends on The SPARQL endpoint answered an ASK with an empty result set, so the Web-Algebra SPARQLString check - an ASK with the query's own pattern, to send a query that matches nothing back to the model - never bit against this platform. The boolean result is Core 5.0.6: SPARQLResult, its provider, and the ASK path of SPARQLEndpointImpl. Core is pinned at 5.0.6-SNAPSHOT ahead of Web-Client's transitive 5.0.5, and SPARQLResultProvider is registered beside ResultSetProvider on the server and on both clients. The pin resolves from a local install only until Core is released, so an image build needs that release, or the snapshot deployed, first. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…he service's reason An envelope holding a message instead of a plan was shown as a reply whatever the status, which read a validator's refusal - a parser's line and column - as the assistant declining in words. A message on a 200 is still a reply; on any other status it is the service's reason for failing, and the card now reports it as the failure it is, through the chain's own failure path, with that reason as the detail rather than the status line. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A step row borrowed its operation's XML from the plan only when the executor's steps and the plan's operations matched one to one, so a plan that failed at its tenth operation of nineteen rendered every row without a fold-out, and so did one still running - only a plan that ran to the end lined up. The steps are now paired with the plan's leading operations, as many as were entered, in the order the executor enters them; a ForEach's iterations are more steps than operations and still get none. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…d onto the create bar The assistant produces blocks, and the drawer kept it where blocks are not: a plan wrote into the document, then the whole page reloaded to catch up, in a panel beside the content it changed. The placement rule already said where it belongs - a form for what is added to the current document renders inline - so it moves there. The conversation is an ephemeral block at the end of the content body, before the create bar, so what a step writes lands above it in order; the composer is a panel docked onto the bar's upper edge, opened by the bar's own button beside Create and closed by it or by Escape. A page with no bar - a proxied resource, a search result - gets a bar holding the composer alone, so reads work everywhere. None of it is server-rendered but the button: a conversation exists only once the client runs, and the body it sits in is replaced on every navigation and every write's catch-up. So the conversation is not the DOM: each document's log is an element kept under the document's URI, the one composer form is kept for the session, and ldh:ChatMount moves them into whatever body was just rendered. A card is painted into its log whether the log is in the page or not, so a plan still running when the body goes finishes into its log and is there when the document is next shown; a card that reported a write comes through the catch-up with its steps, its documents and its fold-outs. Conversations are per document, and the history the planner hears is that document's. The drawer, its edge sensor and its CSS go; the mount is guarded on the WebID itself rather than the agent's document, which document() only has once something loaded it. The assistant spec covers the new surface: opened from the bar and docked above it, a card in the body, Clear from the block's head, and the card surviving the catch-up. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…arts Docked onto the create bar, the composer took the bar's full width: the bar is full-bleed on the left and padded on the right to the column's edge, so the composer ran from the viewport's edge to the Create button while the content it serves sits in a centred column. Its left margin now makes up the column's inset over the bar's own padding, and its width is the column's inside its padding; margin plus width fill the bar's line exactly, which is what wraps the buttons below it. The spec measures it against the column. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…it's block header, its skin in app.css The assistant had no place in the design system - no component, no card, no rule - so its markup could only be read off the XSL. It is one now (ui_kits/app/Assistant.jsx, preview/component-assistant.html), and the product uses the kit's vocabulary: the chat-* classes become ldh-chat-*, the block's head is THE block header at compact density with Clear in its actions cluster, and the rules that are the component's look - the create bar's, the block's, the turns', the plan card's, the step rows', the composer's - move from ldh.css into the vendored app.css, pushed upstream in the same change. What stays in ldh.css is the page's geometry: where the bar parks and how wide the docked composer is, which only this page's column and pane widths can say. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A plan's card showed its rows and its step tree, which is the machinery; the answer the reader asked for was theirs to read off it. Once a plan ends, and once its rows have their labels, the card asks the answer service beside the plan service for a reading - the question, the plan's summary, the steps, the documents written and the labelled rows go, a sentence or two comes back - and that sentence becomes the card's first line. The steps now live under a trace disclosure whose summary is their count and the plan's time: open while the plan is being read and run, since the rows are what there is to see, folded once there is an answer to read instead. The rows stay on the card and stay the authority; a service that cannot answer leaves the card as it was. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…akes it the document's A plan's result rendered as a bare table whatever it was. It now sits in a well of its own under the card, headed like a block with the mode's label, and drawn through the document's own block rendering by the plan's presentation hint: a result set as the results table or, when the plan said chart, as a chart drawn into the card with the hint's axes; a graph as the view block's list, grid or table. What the reader sees in the card is what a block in the document would show. Keep, in the well's actions, writes it there: a plan of the card's own making - the query stored on the document, the endpoint registered as a service first when it is not the dataspace's own, a view in the mode drawn or a chart with the axes drawn, and an object block placing it after the document's last block - run at once as its own card. A document the assistant wrote to comes back in ContentMode, which is the mode that shows its blocks, so the kept block appears above the conversation. The fragments carry their '#', since the waldh: operations resolve <fragment> against <url> as a relative URI. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… and how it went Every write answers a one-row ?status ?url result set, and the card drew each one as a result well with Keep, sent them to the answer pass as the rows - which then said no value was returned - and kept them as the next question's "them". They are the documents written, which the card already lists, so they are left out of all three, and the list counts each document once. The trace folded under a summary that read as a caption: no chevron, no hover, and the green rows seemed to vanish. The summary now has a chevron that turns when open and the outcome's glyph, a green check or a red error, so the folded trace still says how the steps went. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Keep pinned a result into the document as a view or chart built from the card's query. It chose the block by the card's rendering, and turned "what's the current year" into a view over SELECT (YEAR(NOW()) AS ?currentYear), which a view - a list of resources - cannot show, and whose block then failed to render at all. With chats about to be stored in the document, every result persists anyway, and putting one into the document's flow is a request in words that the planner writes as blocks. The button, its write plan and its translations are removed; the result wells stay. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…inds no resources SPARQL.js gives an aliased projection, (?s AS ?resource), as a map rather than the string "?s", and reading only the string left the view's focus variable empty and its whole render failing. ldh:first-var-name reads both shapes. An alias is bound only in the projection, so the rewrites that project something else or append patterns to the WHERE (the result count, the container lookup, the parallax step) first wrap the query as a subquery (ldh:wrap-subquery). Without it the container lookup joined on an unbound variable, matched every triple in the store and exhausted Fuseki's memory. The count's projection and the parallax step's WHERE now match the outer query only, not a subquery's. A first variable that binds values, (YEAR(NOW()) AS ?year), gives rows the DESCRIBE finds nothing for. The count tells the two apart, and the block says the query lists no resources rather than that nothing matched. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…turn by turn, and shows its plans resolved The assistant moves from one ephemeral, client-kept log docked on the create bar to an ldh:Chat resource placed by an ldh:Object block, like a view or a chart. A page holds as many chats as it has chat blocks, each ending in its own composer; the bar's Assistant button starts a new one before the bar, which is written - chat, object block and the document's next rdf:_N, in one conditional POST - on its first question, so a click that asks nothing leaves nothing behind. Every turn is an ldh:ChatTurn, the chat's next rdf:_N, written to the chat's own document once its answer lands: the question, the answer, the plan and what the execution reported (result capped at 100), as XMLLiterals, and the outcome. The write catch-up waits for it, and the chat block's RowHook draws every turn from the store, so a chat survives leaving the page, can be embedded, and continues with its stored history. A reader without acl:Append sees the transcript and no composer. The literals travel escaped, typed rdf:XMLLiteral: Jena's RDF/XML parser refuses an rdf:RDF inside rdf:parseType="Literal", and a turn whose plan read a graph answered 400. The trace shows the plan as it ran: a step that reported a value at run time (the query a SPARQLString wrote, as wa:value) folds it out, and the operation around it shows the call replaced by that value. ldh.ttl and the rdf/ vocabulary gain ldh:Chat, ldh:ChatTurn and the turn's properties; the global mount, the session composer and Clear are gone. tests/ui/specs/shell/assistant.spec.mjs is rewritten around chat blocks (8 specs, 5 without a model). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…nd draws a grid and a timeline A plan's <wa:present> now names the layout mode as ac:mode - ac:TableMode, ac:ListMode, ac:GridMode, ac:ChartMode, the values a view block's ac:mode takes - and a chart's details as the properties a chart block has: ldh:chartType, ldh:categoryVarName, ldh:seriesVarName. ldh:present-mode and ldh:present-chart-type read them, and still read the table/list/grid/chart tokens and the type/category/series attributes of turns stored before, so those draw as they did. A result well carries its mode's URI in data-mode. Specs: a stubbed grid plan whose graph depicts its resources draws one card per resource with its image, live and again from the store; a stubbed timeline plan draws one labelled span per row, the one whose end came from COALESCE included. Both run without a model. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The trace folds out each operation's XML in the kit's code field, which is a flex row built for the kit's own text area, the one child that grows. The trace puts a plain pre there, which nothing told to grow, so a plan of short lines showed its XML in a box as wide as its longest line inside a field as wide as the card. The pre now takes flex: 1 and min-width: 0, as the text area does; long lines still scroll inside it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
# Conflicts: # CHANGELOG.md
…t since #404 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…the overlay exclude go The boolean result of an ASK, which the assistant checks its queries with, shipped in Core 5.0.6 and Web-Client 6.0.4 depends on it. The direct core:5.0.6-SNAPSHOT dependency, which only resolved from a local repository, and the overlay's exclude of its own Core jar are no longer needed; the SPARQLResultProvider registrations compile against the released Core. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…s controls, and a maximised graph fills the viewport - chart.xsl: a bar's length is its value, so the bar chart's value axis takes minValue 0 (ignored when the data goes below it) - app.css: the chart's Save stores what its controls picked, so its footer is hidden while the controls are collapsed - ldh.css: a maximised 3D graph canvas is pinned with inset: 0 and auto size, rather than 100% of a containing block that is not the viewport Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A signed-in reader can ask the assistant for something in words. The request becomes a Web-Algebra plan, written by REST-VKG's
web-algebraservice. The plan is shown with its operations and XML, and only runs on Execute. Read-only plans can run on arrival.A conversation is a block
ldh:Chatis placed in the document by anldh:Objectblock, like a view or a chart. A page holds as many chats as it has chat blocks.rdf:_N, in one conditional POST.ldh:ChatTurn, the chat's nextrdf:_N, written to the chat's own document once its answer lands. A turn holds the question, the answer, the plan and what the execution reported, with the result capped at 100 rows. Plan and execution are escapedrdf:XMLLiterals. The block draws its turns from the store, so a chat survives leaving the page and can be embedded.A plan card
SPARQLString's row also shows the query it resolved to.ac:mode: a table, a list, a grid of image cards, or a chart (bar, line, scatter or timeline).Platform
web-algebraservice (ghcr.io/atomgraph/webalgebra-server-ldh) is reached through nginx at the reserved/webalgebrapath. It acts for the reader through the secretary's delegation. The model key is the optionalopenai_api_keysecret.AGENTS.mdis served at/AGENTS.mdon every dataspace origin, and says the default graph is empty.ldh:Chat,ldh:ChatTurnand the turn properties are inldh.ttland therdf/library.developas a cherry-pick.Depends on
webalgebra-server-ldhimage must include Application not found + Ontology cannot be null errors while attempting to access service launched on Ubuntu host #28 (step values, presentation hint) and Check that certificate password is ASCII #29 (object-block embeds), since the stack runs:latest.openai_api_keysecret, with API credit.Tests
tests/ui/specs/shell/assistant.spec.mjshas 10 specs:ldh, or stub the plan service. They cover the button and draft, a stored chat with history and storing a turn, two independent chats, the anonymous transcript, resolved values in the trace, and grid and timeline wells.tests/ui/specs/document/blocks/view/projection.spec.mjscovers the view fix.🤖 Generated with Claude Code