Skip to content

[#1196] Stream query answers as JSON over the Command Router HTTP API#1197

Open
marcocapozzoli wants to merge 7 commits into
masterfrom
masc/1196-queryanswer-tojson
Open

[#1196] Stream query answers as JSON over the Command Router HTTP API#1197
marcocapozzoli wants to merge 7 commits into
masterfrom
masc/1196-queryanswer-tojson

Conversation

@marcocapozzoli

@marcocapozzoli marcocapozzoli commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Adds QueryAnswer::to_json() to serialize QueryAnswers as structured JSON objects.
  • Adds QueryAnswer::from_json() to rebuild a QueryAnswer from a JSON object.
  • Updates the Command Router HTTP API WebSocket stream to send JSON answers instead of debug strings from to_string().
  • Changes the chunk pipeline to use JSON arrays end-to-end (BusCommandRouterProxyStreamPoller, CommandExecution, CommandRouterHttpAPI).

to_json() with metta_flag=false.

{
  "handles":[["289476dbf62faffc149e3658bc368b6b"]],
  "assignment":{"X":"a408f6dd446cdd4fa56f82e77fe6c870"},
  "metta_expressions":[],
  "assignment_metta":{},
  "importance":0.0,
  "strength":0.0
}

to_json() with metta_flag=true.

{
  "handles":[["289476dbf62faffc149e3658bc368b6b"]],
  "assignment": {"X":"a408f6dd446cdd4fa56f82e77fe6c870"},
  "metta_expressions": [["(Similarity \"human\" \"ent\")"]],
  "assignment_metta":{"X":"\"ent\""},
  "strength":0.0,
  "importance":0.0
}

Resolves #1196

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

The command-router streaming contract now uses JSON arrays instead of vector<string>. Query answers gain structured JSON serialization and deserialization, execution events publish JSON payloads, synchronous responses aggregate JSON results, and tests validate the updated shapes.

Changes

JSON command-router streaming

Layer / File(s) Summary
QueryAnswer JSON serialization
src/agents/query_engine/QueryAnswer.*, src/tests/cpp/query_answer_test.cc
Adds JSON serialization/deserialization for answer data, MeTTa fields, empty answers, invalid input validation, and state preservation on parse errors.
Poller JSON chunk construction
src/agents/command_router/http_api/BusCommandRouterProxyStreamPoller.*
Changes polling callbacks and buffered command output to JSON arrays, including structured query and evolution answers.
Execution and HTTP JSON propagation
src/agents/command_router/http_api/CommandExecution.*, src/agents/command_router/http_api/CommandRouterHttpAPI.*
Validates and publishes JSON array chunks, forwards them through execution callbacks, and returns the first aggregated JSON result for synchronous commands.
JSON streaming validation
src/tests/cpp/command_router_http_api_test.cc
Updates chunk helpers and coverage for query, evolution, count-only, MeTTa, grouping, get, set, and overflow behavior using JSON assertions.

Estimated code review effort: 3 (Moderate) | ~30 minutes

Sequence Diagram(s)

sequenceDiagram
  participant BusCommandRouterProxyStreamPoller
  participant QueryAnswer
  participant CommandRouterHttpAPI
  participant CommandExecution
  BusCommandRouterProxyStreamPoller->>QueryAnswer: to_json(metta_flag)
  QueryAnswer-->>BusCommandRouterProxyStreamPoller: structured JSON answer
  BusCommandRouterProxyStreamPoller->>CommandRouterHttpAPI: JSON array chunk
  CommandRouterHttpAPI->>CommandExecution: publish_chunk(seq, data)
  CommandExecution-->>CommandRouterHttpAPI: JSON event published
Loading

Possibly related PRs

  • singnet/das#1165: Introduces the execution and streaming workflow extended by this JSON chunk refactor.
  • singnet/das#1175: Adds synchronous get/set result aggregation in the execution path updated here.
  • singnet/das#1188: Changes command-router streaming and count-only handling in the same implementation area.

Suggested reviewers: ccgsnet, andre-senna

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change: streaming query answers as JSON through the Command Router HTTP API.
Description check ✅ Passed The description accurately summarizes the JSON serialization and streaming pipeline changes in this PR.
Linked Issues check ✅ Passed The PR implements QueryAnswer::to_json() and uses JSON instead of to_string(), satisfying #1196.
Out of Scope Changes check ✅ Passed No clearly unrelated changes stand out; the added JSON plumbing and tests support the same streaming/serialization goal.
Tests For Behavior Changes ✅ Passed Behavior-changing src/ code was paired with updated C++ tests in src/tests/cpp/query_answer_test.cc and src/tests/cpp/command_router_http_api_test.cc.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch masc/1196-queryanswer-tojson

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/tests/cpp/command_router_http_api_test.cc (1)

385-397: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover populated MeTTa mappings and scalar fields.

This only verifies raw-value fallback. Add a JSON serialization or stream test with metta_expression populated, asserting the "metta" key, mapped assignment, importance, and strength.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/tests/cpp/command_router_http_api_test.cc` around lines 385 - 397, Extend
the existing poll_stream test around
BusCommandRouterProxyStreamPoller::poll_stream with a populated metta_expression
case, using JSON serialization or streamed output. Assert the resulting JSON
includes the "metta" key, the mapped assignment, and the expected scalar
importance and strength values, while retaining the current raw-value fallback
assertions.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/tests/cpp/command_router_http_api_test.cc`:
- Around line 385-397: Extend the existing poll_stream test around
BusCommandRouterProxyStreamPoller::poll_stream with a populated metta_expression
case, using JSON serialization or streamed output. Assert the resulting JSON
includes the "metta" key, the mapped assignment, and the expected scalar
importance and strength values, while retaining the current raw-value fallback
assertions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 457f3207-23d5-4116-9c49-790b2344988d

📥 Commits

Reviewing files that changed from the base of the PR and between 2d8f7bf and eef6df3.

📒 Files selected for processing (9)
  • src/agents/command_router/http_api/BusCommandRouterProxyStreamPoller.cc
  • src/agents/command_router/http_api/BusCommandRouterProxyStreamPoller.h
  • src/agents/command_router/http_api/CommandExecution.cc
  • src/agents/command_router/http_api/CommandExecution.h
  • src/agents/command_router/http_api/CommandRouterHttpAPI.cc
  • src/agents/command_router/http_api/CommandRouterHttpAPI.h
  • src/agents/query_engine/QueryAnswer.cc
  • src/agents/query_engine/QueryAnswer.h
  • src/tests/cpp/command_router_http_api_test.cc

Comment thread src/agents/command_router/http_api/BusCommandRouterProxyStreamPoller.cc Outdated
Comment thread src/agents/query_engine/QueryAnswer.cc
Comment thread src/agents/query_engine/QueryAnswer.cc

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/tests/cpp/query_answer_test.cc (1)

507-538: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add coverage for the remaining from_json validation branches.

This test exercises several RAISE_ERROR paths but misses two introduced by this PR: the handles/assignment size-limit checks (QueryAnswer.cc Lines 220-223, 251-254), and a handles[i] element that is not itself an array (e.g. {"handles": [1]}), which is distinct from the already-tested "handle element has wrong type" case.

As per path instructions, "TEST COVERAGE: Behavior changes here should have matching *_test.cc updates; suggest concrete test cases (edge cases, error paths, concurrency) not trivial assertions."

json handles_not_array = {{"strength", 0.0},
                          {"importance", 0.0},
                          {"handles", json::array({1})},
                          {"assignment", json::object()},
                          {"metta_expression", json::object()}};
EXPECT_THROW(QueryAnswer().from_json(handles_not_array), runtime_error);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/tests/cpp/query_answer_test.cc` around lines 507 - 538, Extend
QueryAnswer.from_json edge-case coverage in from_json_edge_cases with tests for
handles and assignment collections exceeding their validation size limits,
asserting runtime_error. Also add a case where handles contains a scalar element
such as 1, distinct from an array element with an invalid handle type, and
assert the same exception.

Source: Path instructions

src/agents/query_engine/QueryAnswer.cc (1)

143-192: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Unconditional metta JSON allocations even when metta_flag is false.

metta_group_json is constructed for every handle group (and metta_handles_json/assignment_metta_json once) even when metta_flag is false. Since nlohmann::json stores arrays/objects as heap pointers, each json::array()/json::object() call allocates. The else branch at Lines 183-186 then discards these and builds fresh empty containers instead of reusing them — so this work is 100% wasted on the (likely default/common) non-metta path of what appears to be a per-answer streaming hot path.

As per path instructions, "Avoid heap allocations in hot paths ... MEMORY & PERFORMANCE (high priority)".

⚡ Proposed fix: only allocate metta containers when needed
-    json handles_json = json::array();
-    json metta_handles_json = json::array();
+    json handles_json = json::array();
+    json metta_handles_json = metta_flag ? json::array() : json();
     for (const vector<string>& group : this->handles) {
         json group_json = json::array();
-        json metta_group_json = json::array();
+        json metta_group_json = metta_flag ? json::array() : json();
         for (const string& handle : group) {
             group_json.push_back(handle);
             if (metta_flag) {
                 auto it = this->metta_expression.find(handle);
                 metta_group_json.push_back(
                     (it != this->metta_expression.end() && !it->second.empty()) ? it->second : handle);
             }
         }
         handles_json.push_back(group_json);
         if (metta_flag) {
             metta_handles_json.push_back(metta_group_json);
         }
     }

     json assignment_json = json::object();
-    json assignment_metta_json = json::object();
+    json assignment_metta_json = metta_flag ? json::object() : json();
     for (const auto& pair : this->assignment.table) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/agents/query_engine/QueryAnswer.cc` around lines 143 - 192, Update
QueryAnswer::to_json so metta_handles_json, assignment_metta_json, and each
per-group metta_group_json are allocated only when metta_flag is true. Preserve
the existing metta output when enabled, while assigning the required empty
array/object directly in the false branch without creating discarded containers
on the non-metta path.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/agents/query_engine/QueryAnswer.cc`:
- Around line 194-277: Update QueryAnswer::from_json so all strength,
importance, handles, assignment, and metta_expression validation completes
before mutating this, or build the parsed result in temporary state and commit
it only after success. Preserve the existing successful parsing behavior while
ensuring any RAISE_ERROR leaves the current QueryAnswer unchanged or in a
consistently reset state.

---

Nitpick comments:
In `@src/agents/query_engine/QueryAnswer.cc`:
- Around line 143-192: Update QueryAnswer::to_json so metta_handles_json,
assignment_metta_json, and each per-group metta_group_json are allocated only
when metta_flag is true. Preserve the existing metta output when enabled, while
assigning the required empty array/object directly in the false branch without
creating discarded containers on the non-metta path.

In `@src/tests/cpp/query_answer_test.cc`:
- Around line 507-538: Extend QueryAnswer.from_json edge-case coverage in
from_json_edge_cases with tests for handles and assignment collections exceeding
their validation size limits, asserting runtime_error. Also add a case where
handles contains a scalar element such as 1, distinct from an array element with
an invalid handle type, and assert the same exception.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: e32f548e-8d8c-4375-82b2-36303f4494db

📥 Commits

Reviewing files that changed from the base of the PR and between eef6df3 and 79384b1.

📒 Files selected for processing (4)
  • src/agents/command_router/http_api/BusCommandRouterProxyStreamPoller.cc
  • src/agents/query_engine/QueryAnswer.cc
  • src/agents/query_engine/QueryAnswer.h
  • src/tests/cpp/query_answer_test.cc
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/agents/command_router/http_api/BusCommandRouterProxyStreamPoller.cc

Comment thread src/agents/query_engine/QueryAnswer.cc
Comment thread src/agents/query_engine/QueryAnswer.cc Outdated
Comment thread src/agents/query_engine/QueryAnswer.cc Outdated
Comment thread src/agents/query_engine/QueryAnswer.cc Outdated
Comment thread src/agents/query_engine/QueryAnswer.cc Outdated
Comment thread src/agents/query_engine/QueryAnswer.cc Outdated
Comment thread src/agents/query_engine/QueryAnswer.cc Outdated
Comment thread src/tests/cpp/query_answer_test.cc
@andre-senna
andre-senna self-requested a review July 24, 2026 18:01

@andre-senna andre-senna left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@marcocapozzoli
Please address my last comment before merging.

Comment thread src/agents/query_engine/QueryAnswer.cc Outdated
}
}

this->strength = strength;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand the point of having this "summarization" of everything that's required to build the QueryAnswer object but because we have this->handles and this->assignment (which are not scalar types) we are ending up doing unnecessary copies. Therefore, I suggest using "this->*" directly in each place above in order to avoid this unnecessary copy here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Create a to_json() method in QueryAnswer.

2 participants