Class: Silas::Engines::AgentSdk

Inherits:
Base
  • Object
show all
Defined in:
lib/silas/engines/agent_sdk.rb

Overview

The engine-owned adapter: one claude -p subprocess runs a whole agentic turn, calling back into Silas tools over an in-worker HTTP MCP endpoint whose tools/call goes THROUGH the Ledger (exactly-once within the run). The mirror image of :ruby_llm — Claude Code owns the loop; Silas hosts the tools and maps the NDJSON stream onto durable rows.

v1 contract (honestly weaker than :ruby_llm): exactly-once WITHIN a run, approval :never tools only, and fail-closed on mid-subprocess worker kill.

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.loop_ownershipObject



12
# File 'lib/silas/engines/agent_sdk.rb', line 12

def self.loop_ownership = :engine

Instance Method Details

#execute_step(context, &on_event) ⇒ Object



14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/silas/engines/agent_sdk.rb', line 14

def execute_step(context, &on_event)
  turn = context[:turn]
  Silas::AgentSdk::VersionGuard.assert!(Silas.config.agent_sdk_claude_bin)
  assert_api_key!

  tools = allowed_tools(context[:tools])
  server = Mcp::Server.start(turn: turn, step: context[:step], tools: tools, resolver: Silas.tool_resolver)
  cli = nil
  begin
    server.await_ready!
    cli = build_cli(context, server, tools)
    parser = Silas::AgentSdk::StreamParser.new
    cli.stream do |line|
      parser.ingest(line) do |event|
        persist_session_id!(turn, parser)
        on_event&.call(event)
        ActiveSupport::Notifications.instrument("silas.agent_sdk.event", turn_id: turn.id, type: event.type)
      end
    end
    parser.to_result
  ensure
    cli&.terminate
    server.stop
  end
end