NeoGraph 0.16.0
A C++20 Graph Agent Engine Library — LangGraph for C++
Loading...
Searching...
No Matches
neograph::acp::ACPServer Class Reference

HTTP-less, stdio-first ACP server hosting a NeoGraph engine. More...

#include <server.h>

Public Types

using NotificationSink = std::function< void(const neograph::json &)>
 Sink for agent→client notifications (session/update). Receives the fully-formed JSON-RPC envelope ready to be written to the transport.
 

Public Member Functions

 ACPServer (neograph::graph::GraphExecution execution, neograph::json info, std::shared_ptr< ACPGraphAdapter > adapter={})
 Preferred constructor: restrict the host to graph execution.
 
 ACPServer (std::shared_ptr< neograph::graph::GraphEngine > engine, neograph::json info, std::shared_ptr< ACPGraphAdapter > adapter={})
 
void attach_client (std::shared_ptr< ACPClient > c)
 Install a pre-constructed client and bind it to this server. Useful when the engine's nodes captured a client at compile() time before the server existed: pass that same shared_ptr here to make it usable. Replaces any previously cached client.
 
neograph::json call_client (std::string method, neograph::json params, std::chrono::milliseconds timeout=std::chrono::seconds(30))
 Issue a JSON-RPC request to the connected client and block until a response arrives. Used internally by ACPClient; exposed publicly so user code can issue protocol extensions. fs/*, terminal/* and elicitation/* require the matching client capability. When params.sessionId names a session with a prompt turn in flight, cancelling that turn ends the wait (after the grace described at stop) with neograph::graph::CancelledException. timeout of std::chrono::milliseconds::max() waits without a deadline. Throws ACPClientError on an error envelope, and std::runtime_error on transport not yet connected, on a missing capability and on timeout.
 
AgentCapabilities & capabilities ()
 Mutable access to the capabilities advertised in initialize. Defaults: session.resume=true, load_session=false, no prompt extras, no MCP transports (stdio is required by spec — its presence is implicit).
 
std::shared_ptr< ACPClient > client ()
 Handle for issuing agent→client requests (fs/*, etc.). On first call lazily creates a client bound to this server and caches it. Subsequent calls return the same instance. Nodes in the graph capture this handle to talk back to the editor.
 
neograph::json handle_message (const neograph::json &envelope)
 Process exactly one parsed JSON-RPC envelope. Returns:
 
bool is_running () const
 True while run() is actively reading from its input stream. Set on entry, cleared on exit. Mirrors neograph::a2a::A2AServer::is_running so a generic supervisor can poll either without protocol-specific casing.
 
void run ()
 Convenience overload — uses std::cin / std::cout.
 
void run (std::istream &in, std::ostream &out)
 Drive the server loop reading newline-delimited JSON-RPC envelopes from in and writing them to out. Returns when in reaches EOF or stop() is called from another thread.
 
std::vector< std::shared_ptr< neograph::Tool > > session_tools (std::string_view session_id) const
 The tools of a session's client-supplied MCP servers; see ACPClient::session_tools.
 
void set_eof_grace (std::chrono::milliseconds grace)
 How long run() lets in-flight prompts finish after its input reaches EOF (the client closed the connection) before it cancels them. Defaults to one second.
 
void set_max_inflight_prompts (std::size_t limit)
 Bound concurrently executing sessions; reject excess prompts before any graph node runs (JSON-RPC -32603 with data.reason "overloaded"). Defaults to 32.
 
void set_notification_sink (NotificationSink sink)
 Where session/update notifications go. Default: dropped on the floor (test-friendly). When run() is driving, the sink is set to write to the output stream automatically. A sink retained by a shared_ptr-owned server must capture that server weakly to avoid an ownership cycle. Objects captured by reference must outlive the server (or clear the sink before they are destroyed); the server drops late worker notifications once its destructor begins.
 
void set_pre_armed_cancel (bool enabled)
 Opt in to the pre-armed cancel: a session/cancel received after session/new but before the session's first prompt cancels that prompt. Off by default — on an ordered transport such as stdio a cancel with no prompt running is stale and must not cancel a later turn.
 
void stop ()
 Signal a running run() loop to exit at the next message boundary.
 

Detailed Description

HTTP-less, stdio-first ACP server hosting a NeoGraph engine.

Lifecycle:

ACPServer server(engine, info); server.run(); // blocks: reads std::cin, writes std::cout

The single-shot handle_message form is provided for testing and for embedding inside a custom transport (HTTP, WebSocket, in-process pipe). It takes one parsed JSON-RPC envelope and returns the response envelope (or a null json for notifications); side-effect notifications (session/update) are delivered through the NotificationSink set on the server.

Definition at line 375 of file server.h.

Constructor & Destructor Documentation

◆ ACPServer()

neograph::acp::ACPServer::ACPServer ( std::shared_ptr< neograph::graph::GraphEngine >  engine,
neograph::json  info,
std::shared_ptr< ACPGraphAdapter >  adapter = {} 
)
Parameters
engineThe graph engine that handles incoming prompts.
infoagentInfo returned in initialize: an ACP Implementation {"name":"my-agent","version":"0.1.0"} with an optional string title and object _meta. A missing or non-string name/version is filled with "neograph-agent" / the NeoGraph version, and any other key is dropped, so the reply always validates.
adapterOptional input/output mapping override.

Member Function Documentation

◆ handle_message()

neograph::json neograph::acp::ACPServer::handle_message ( const neograph::json &  envelope)

Process exactly one parsed JSON-RPC envelope. Returns:

  • the response envelope for requests ({ "jsonrpc","id","result"|"error" })
  • a null json (no value) for notifications. Side-effect notifications go to the sink set via set_notification_sink.

◆ run()

void neograph::acp::ACPServer::run ( std::istream &  in,
std::ostream &  out 
)

Drive the server loop reading newline-delimited JSON-RPC envelopes from in and writing them to out. Returns when in reaches EOF or stop() is called from another thread.

Lifecycle parallel with neograph::a2a::A2AServer: run() here is the synchronous read-loop equivalent of A2A's start() (also synchronous, blocks until shutdown). Neither server exposes a built-in async-spawn helper because the host embedder usually picks the threading model; spawn run() on a std::thread if you want it to run in the background.

◆ stop()

void neograph::acp::ACPServer::stop ( )

Signal a running run() loop to exit at the next message boundary.

Cancellation semantics: ACP session/cancel signals the prompt's CancelToken, so engine checkpoints and cancellation-aware providers/tools stop cooperatively. A node that neither polls its token nor awaits a cancellation-aware operation still runs to its next engine checkpoint. The terminal session/prompt response then carries StopReason::Cancelled.

session/cancel cancels the session's ongoing prompt turn; a cancel with no prompt running is ignored. A $/cancel_request that names an in-flight session/prompt by its id does the same for that turn (its response is the cancelled stop reason); one that names an unknown or finished request, or any other request, is ignored. An embedder whose transport can reorder messages may opt into set_pre_armed_cancel, under which a cancel that arrives before a session's first prompt cancels it.

While a turn waits for an agent→client request (fs/*, terminal/*, session/request_permission), session/cancel gives the client a short grace to answer on its own (ACP requires a client to answer pending permission requests with cancelled); after it the server sends $/cancel_request for the request and stops waiting.

Destructor semantics: ~ACPServer joins all in-flight session/prompt worker threads before returning. Each worker blocks until its engine->run() completes, so destroying the server during a 60-second LLM call blocks the destroying thread for the full duration. Drain or cancel before destruction if you need a bounded-latency teardown.


The documentation for this class was generated from the following file: