feat(agent): Java Agent builder + durable java_tool worker (Track 9 Phase B)#9
Conversation
…ck round-trip New jamjet-agent module with JamjetEngineClient: JDK java.net.http on a virtual-thread executor, JamjetJson snake_case bodies, BARE engine routes (no /api/v1). Mirrors the canonical Python jamjet.client shapes. - createWorkflow/startExecution/getExecution/listEvents - claimWorkItem/completeWorkItem/failWorkItem/heartbeatWorkItem - lease_fence threaded on heartbeat and complete (engine PR #108); a stale fence on complete surfaces as JamjetHttpException with isConflict()==true so a reclaimed worker can detect a lost lease - startExecution uses the workflow_version body key (matches client.py) - complete includes lease_fence only when non-null Hermetic WireMock test proves the wire shape: create -> start -> poll to Completed, plus the work-item path (claim returns lease_fence, complete and heartbeat post it, null fence is omitted, stubbed 409 surfaces as conflict).
… IR with JavaFn nodes Add NodeKind.JavaFn (snake_case java_fn: class_name/method/output_schema) + QueueType.JAVA_TOOL (java_tool), matching the Phase A Rust engine, plus a tools field on NodeKind.Model carrying OpenAI schemas. Stop serializing the computed queueType()/isDurable() helpers (@JsonIgnore) so a node matches the Rust shape exactly. Build the dev.jamjet.agent module: a @tool method annotation, a ToolRegistry that extracts OpenAI-format schemas keyed by class+method (the single source for authoring and B-3 dispatch), and an Agent builder whose compileToIr() emits the static-unroll agent-loop WorkflowIr (Model -> tool gate -> JavaFn dispatch -> next Model, startNode always the first Model node) with governance folded into policy/token_budget/cost_budget_usd/data_policy. A golden-file parity test asserts the Java IR matches the Python compile_agent_to_ir output structurally (node ids, edges, gate branches, model tool schemas, governance, turn count), with the documented tool-node difference (java_fn vs python_fn) asserted explicitly.
…ated dispatch)
B-3 of Track 9 Phase B. Adds the ToolDispatcher the JavaFn nodes point at and
the durable worker that drains the java_tool queue, mirroring the hardened Python
worker and dispatch_tool_calls.
ToolDispatcher.dispatchToolCalls reads last_model_tool_calls / last_model_output
(with the tool_calls / assistant_content fallbacks) from the work item input,
resolves each call by name through the ToolRegistry only, coerces JSON args to the
typed method params via Jackson, invokes reflectively, and returns the updated
message list as {messages:[...]} (the node output and the state_patch). An unknown
tool yields a clean tool error to the model, never an arbitrary invocation.
JavaToolWorker claims java_tool items and settles them exactly-once:
- completes WITH the claim lease_fence (the engine fences /complete; PR #108)
- treats a 409 on complete as a lost lease no-op, never failing the item
- runs a heartbeat that, on a definitive engine rejection, aborts the in-flight
tool and skips completion (M3); the engine surfaces a lost heartbeat lease as 500
- gates dispatch to the one fixed ToolDispatcher coordinate, never Class.forName on
a payload field (the RCE gate)
Tests cover the happy path (complete with fence), fence-lost mid-flight (no
complete), 409 = lost lease (no fail), a forged dispatch coordinate (clean fail,
not RCE), and arg coercion. mvn -pl jamjet-agent -am verify green (50 tests).
Add the durable run path to the Java Agent builder, mirroring the Python Agent.run_durable: compile -> create_workflow -> start_execution (seeded with the system+user messages and the tool-resolver map) -> poll get_execution to a terminal state -> extract the final assistant text + tool-call trace into an AgentResult. - AgentResult (output + toolCalls + raw terminal state) and a nested ToolCall record, parallel to the Python result shape. - RunOptions (maxTurns / runtimeUrl / auth / pollInterval / timeout) with defaults and withers; timeout derives from the agent when unset. - AgentRunException (non-completed terminal: failed / cancelled / limit_exceeded) and AgentRunTimeoutException, mirroring the Python RuntimeError / TimeoutError contract. - DurableRunner orchestrates create/start/poll/extract. Terminal statuses are the snake_case the engine serializes (completed / failed / cancelled / limit_exceeded). The answer is current_state.last_model_output (written inline by the Rust Model executor's state_patch), falling back to the last assistant message; tool calls reconstruct from current_state.messages. The existing ExecutionState.currentState carries both, so no record change. - AgentIrCompiler.buildInitialState / toolsMap seed the run input. - DRY (B-3 review): AgentIrCompiler.DISPATCH_CLASS/METHOD now reference the ToolDispatcher constants the worker's RCE gate checks, so the coordinate the builder emits and the coordinate the worker accepts are one literal. Tests: WireMock-driven create/start/poll/extract, the last-assistant fallback, terminal failed/limit_exceeded surfacing, the poll timeout, and the client-owning vs client-injecting overloads. The parity golden stays green after the DRY fix.
An idiomatic Java example under jamjet-agent: a calculator Agent with a @tool plus governance defaults (token+cost budget, inline policy with a blocked tool + model allowlist, an approval gate, PII), authored in clean Java and runnable durable+governed. - CalculatorTools: one @tool method (calculate). - CalculatorAgentExample: buildAgent() is the whole authoring surface; main runs it durably with a background JavaToolWorker, documenting the run requirement (engine + model sidecar + a java_tool worker). Real shipped API only, no fictional methods. - CalculatorAgentExampleTest: hermetic end-to-end against a WireMock stub of the bare engine routes. Builds the agent, asserts governance rode into the IR, dispatches the @tool durably through the worker (the tool actually ran: calculate(5,3,add) -> 8 in the completion body), and runDurable extracts the final assistant text + tool call. The result shape parallels the Python run_durable result. Also asserts the worker accepts EXACTLY the java_fn coordinate the compiler emits (the claim payload uses the IR's emitted coordinate), proving the DRY single-source dispatch coordinate end-to-end. - README: idiomatic authoring + the durable-run requirement.
|
Warning Review limit reached
More reviews will be available in 15 minutes and 44 seconds. Learn how PR review limits work. Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file). ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits. 🚦 How do rate limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (19)
📝 WalkthroughWalkthroughIntroduces the Changesjamjet-agent SDK
Sequence Diagram(s)sequenceDiagram
rect rgba(100, 149, 237, 0.5)
Note over Caller,Engine: Agent.runDurable
end
participant Caller
participant Agent
participant DurableRunner
participant JamjetEngineClient
participant Engine as JamJet Engine
Caller->>Agent: runDurable(prompt, options)
Agent->>JamjetEngineClient: new(runtimeUrl, auth)
Agent->>DurableRunner: run(agent, prompt, client, options)
DurableRunner->>JamjetEngineClient: createWorkflow(WorkflowIr)
JamjetEngineClient->>Engine: POST /workflows
Engine-->>JamjetEngineClient: workflowId
DurableRunner->>JamjetEngineClient: startExecution(workflowId, initialState)
JamjetEngineClient->>Engine: POST /executions
Engine-->>JamjetEngineClient: executionId
loop poll until terminal
DurableRunner->>JamjetEngineClient: getExecution(executionId)
JamjetEngineClient->>Engine: GET /executions/{id}
Engine-->>JamjetEngineClient: ExecutionState(status, currentState)
end
DurableRunner->>DurableRunner: extractResult(terminalState)
DurableRunner-->>Caller: AgentResult(output, toolCalls, terminalState)
sequenceDiagram
rect rgba(144, 238, 144, 0.5)
Note over JavaToolWorker,Tool: JavaToolWorker lease-fenced dispatch
end
participant JavaToolWorker
participant JamjetEngineClient
participant ToolDispatcher
participant Tool as `@Tool` method
JavaToolWorker->>JamjetEngineClient: claimWorkItem(java_tool)
JamjetEngineClient-->>JavaToolWorker: ClaimedWorkItem(leaseFence)
JavaToolWorker->>JavaToolWorker: validate dispatch coordinate
JavaToolWorker->>ToolDispatcher: dispatchToolCalls(input) [virtual thread]
JavaToolWorker->>JamjetEngineClient: heartbeatWorkItem(leaseFence) [scheduled]
ToolDispatcher->>Tool: method.invoke(args)
Tool-->>ToolDispatcher: result
ToolDispatcher-->>JavaToolWorker: {messages}
JavaToolWorker->>JamjetEngineClient: completeWorkItem(leaseFence, output)
Note right of JavaToolWorker: 409 → LOST_LEASE (no-op)
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 11
🧹 Nitpick comments (2)
jamjet-runtime-core/src/main/java/dev/jamjet/runtime/core/ir/NodeKind.java (1)
65-70: 🗄️ Data Integrity & Integration | 🔵 Trivial | 🏗️ Heavy liftFreeze
toolsrecursively, not just the outer list.
List.copyOf(tools)still aliases each schema map/list. Because these tool schemas are nested JSON objects, callers can mutate them afterModelconstruction and change the serialized IR/version under an otherwise immutable node. Recursively copy/freeze the schema tree before storing it.🤖 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 `@jamjet-runtime-core/src/main/java/dev/jamjet/runtime/core/ir/NodeKind.java` around lines 65 - 70, The Model.tools normalization only freezes the outer list, while the nested schema maps/lists remain mutable and can still be changed after construction. Update the Model constructor in NodeKind.Model to recursively deep-copy and freeze each tool schema object before assigning tools, so the stored IR cannot be mutated through shared nested references. Use the Model.tools field and its constructor normalization as the place to apply the deep-freeze logic.jamjet-runtime-core/src/test/java/dev/jamjet/runtime/core/ir/NodeKindSerializationTest.java (1)
74-85: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winAdd a missing-field deserialize regression.
This only round-trips the new 4-arg constructor. It does not pin the backward-compat path for older model JSON where
toolsis absent entirely. Add a literal JSON deserialize assertion for that case.🤖 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 `@jamjet-runtime-core/src/test/java/dev/jamjet/runtime/core/ir/NodeKindSerializationTest.java` around lines 74 - 85, The existing NodeKindSerializationTest only covers round-tripping the 4-arg NodeKind.Model constructor, so add a regression test for deserializing older model JSON where the tools field is completely missing. In NodeKindSerializationTest, add a literal JSON deserialize assertion using NodeKind.Model to verify mapper.readValue handles absent tools the same as an empty list, and keep the assertion focused on the backward-compat path rather than serialization output.
🤖 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 `@jamjet-agent/pom.xml`:
- Around line 33-36: The shared Jackson baseline is still pinned to an older
vulnerable databind release, so update the root `jackson.version` property in
the BOM that `jamjet-agent` inherits from to a patched version such as `2.18.8`
or newer. Make the change in the shared Maven property, not in the
`jackson-databind` dependency declaration itself, so all modules using the BOM
pick up the safer version consistently.
In `@jamjet-agent/src/main/java/dev/jamjet/agent/Agent.java`:
- Around line 311-315: The Builder.timeoutSeconds method in Agent should reject
non-positive values before assigning the field. Add validation in
timeoutSeconds(int timeoutSeconds) to ensure timeoutSeconds > 0, and fail fast
with an appropriate exception so invalid values cannot reach the compiled
workflow timeout or durable-run deadline.
- Around line 317-320: Reject blank model ids in Agent.Builder.build() by
validating the model string after Objects.requireNonNull(model, ...) and before
constructing the Agent. Update build() (and any nearby builder validation
helpers) to trim the value and throw an IllegalArgumentException for empty or
whitespace-only model ids, so invalid input is caught early instead of later in
AgentIrCompiler.litellmModel().
In `@jamjet-agent/src/main/java/dev/jamjet/agent/Budget.java`:
- Around line 16-23: The Budget record constructor currently only rejects
non-positive values, so null/null and non-finite doubles still slip through;
update the compact constructor in Budget to treat an entirely empty budget as
invalid and to reject any non-finite costUsd values (NaN/Infinity) and any
non-finite token values if applicable. Use the existing Budget validation block
to enforce that at least one field is provided and that both tokens and costUsd,
when present, are finite and positive before allowing construction.
In `@jamjet-agent/src/main/java/dev/jamjet/agent/client/JamjetEngineClient.java`:
- Around line 154-163: `heartbeatWorkItem(...)` is forcing `lease_fence` to be
always present, which breaks the nullable fence flow already supported by
`completeWorkItem(...)` and `ClaimedWorkItem.leaseFence()`. Update
`JamjetEngineClient.heartbeatWorkItem` to accept a nullable `Long` lease fence
instead of a primitive `long`, and build the request body so `lease_fence` is
omitted entirely when the value is null. Keep the existing optional behavior
consistent with the completion path and preserve the legacy null fence case
without requiring a sentinel value.
In `@jamjet-agent/src/main/java/dev/jamjet/agent/RunOptions.java`:
- Around line 39-49: RunOptions currently validates pollInterval but allows
non-positive timeout values, so add a timeout validation in the RunOptions
constructor to reject Duration.ZERO and negative durations with an
IllegalArgumentException. Update the existing record validation logic alongside
maxTurns, runtimeUrl, and pollInterval, and ensure the check targets the timeout
field used by the durable runner’s deadline calculation.
In `@jamjet-agent/src/main/java/dev/jamjet/agent/tools/ToolDispatcher.java`:
- Around line 150-163: Reject missing required tool arguments before coercing
them in ToolDispatcher.coerceToParameters: the current lookup by
Parameter.getName() silently turns absent fields into null/default values, even
though ToolRegistry.buildInputSchema() marks them required. Add an explicit
arguments.containsKey(...) check for each parameter and fail fast with a clear
validation error before invoking the tool; keep the fix localized to
coerceToParameters and the argument coercion path so bad JSON or omitted fields
cannot reach the tool execution.
In
`@jamjet-agent/src/main/java/dev/jamjet/agent/tools/ToolInvocationException.java`:
- Around line 19-21: The ToolInvocationException constructor is exposing raw
cause text by embedding cause.toString() in the public exception message, which
then gets logged and persisted by JavaToolWorker.process and failWorkItem.
Update ToolInvocationException so the surfaced message stays generic while still
passing the original cause to super(...) and preserving toolName, and ensure any
logging in JavaToolWorker.process relies on the attached throwable rather than
the exception message text.
In `@jamjet-agent/src/main/java/dev/jamjet/agent/tools/ToolRegistry.java`:
- Around line 101-111: Reject overloaded `@Tool` methods in ToolRegistry before
adding them to the registry maps. The current RegisteredTool.key() used by byKey
only relies on class name and method name, so a second annotated overload in the
same holder can overwrite the first and later make byClassAndMethod() resolve
the wrong Method. Update ToolRegistry.register(...) to either make the durable
key include parameter types or throw an IllegalArgumentException when another
`@Tool` with the same method name is already present for that class, keeping
byName and the exported schemas consistent.
In `@jamjet-agent/src/main/java/dev/jamjet/agent/worker/JavaToolWorker.java`:
- Around line 142-145: The `JavaToolWorker.run()` interruption handling is
incorrectly treating `InterruptedException` from `dispatchFuture.get()` as a
failure path, which can settle the item as `/fail` during shutdown or
cancellation. Update the `run()` control flow to handle `InterruptedException`
separately from other exceptions, restore the thread’s interrupt flag, and exit
without failing or settling the item so it remains reclaimable by another
worker. Keep the existing `leaseLost` logic for genuine lease-expiry cases, but
ensure interruption never reaches the failure branch.
In `@jamjet-agent/src/test/java/dev/jamjet/agent/examples/CalculatorTools.java`:
- Around line 23-35: The CalculatorTools.calculate example still uses binary
floating-point, so it can produce imprecise results and returns Infinity for
zero divisors. Update the calculate method to use decimal arithmetic with
explicit handling for divide-by-zero, and adjust the whole-number rendering
logic accordingly; if you keep double-based math, then the “precise/exact”
wording in the example docs should be relaxed instead.
---
Nitpick comments:
In `@jamjet-runtime-core/src/main/java/dev/jamjet/runtime/core/ir/NodeKind.java`:
- Around line 65-70: The Model.tools normalization only freezes the outer list,
while the nested schema maps/lists remain mutable and can still be changed after
construction. Update the Model constructor in NodeKind.Model to recursively
deep-copy and freeze each tool schema object before assigning tools, so the
stored IR cannot be mutated through shared nested references. Use the
Model.tools field and its constructor normalization as the place to apply the
deep-freeze logic.
In
`@jamjet-runtime-core/src/test/java/dev/jamjet/runtime/core/ir/NodeKindSerializationTest.java`:
- Around line 74-85: The existing NodeKindSerializationTest only covers
round-tripping the 4-arg NodeKind.Model constructor, so add a regression test
for deserializing older model JSON where the tools field is completely missing.
In NodeKindSerializationTest, add a literal JSON deserialize assertion using
NodeKind.Model to verify mapper.readValue handles absent tools the same as an
empty list, and keep the assertion focused on the backward-compat path rather
than serialization output.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: 72fa4638-b1db-45dc-b3ff-962b99a66a3b
📒 Files selected for processing (38)
jamjet-agent/pom.xmljamjet-agent/src/main/java/dev/jamjet/agent/Agent.javajamjet-agent/src/main/java/dev/jamjet/agent/AgentIrCompiler.javajamjet-agent/src/main/java/dev/jamjet/agent/AgentResult.javajamjet-agent/src/main/java/dev/jamjet/agent/AgentRunException.javajamjet-agent/src/main/java/dev/jamjet/agent/AgentRunTimeoutException.javajamjet-agent/src/main/java/dev/jamjet/agent/Budget.javajamjet-agent/src/main/java/dev/jamjet/agent/DurableRunner.javajamjet-agent/src/main/java/dev/jamjet/agent/RunOptions.javajamjet-agent/src/main/java/dev/jamjet/agent/Tool.javajamjet-agent/src/main/java/dev/jamjet/agent/client/ClaimedWorkItem.javajamjet-agent/src/main/java/dev/jamjet/agent/client/CreateWorkflowResult.javajamjet-agent/src/main/java/dev/jamjet/agent/client/ExecutionState.javajamjet-agent/src/main/java/dev/jamjet/agent/client/JamjetEngineClient.javajamjet-agent/src/main/java/dev/jamjet/agent/client/JamjetHttpException.javajamjet-agent/src/main/java/dev/jamjet/agent/client/StartExecutionResult.javajamjet-agent/src/main/java/dev/jamjet/agent/tools/RegisteredTool.javajamjet-agent/src/main/java/dev/jamjet/agent/tools/ToolDispatcher.javajamjet-agent/src/main/java/dev/jamjet/agent/tools/ToolInvocationException.javajamjet-agent/src/main/java/dev/jamjet/agent/tools/ToolRegistry.javajamjet-agent/src/main/java/dev/jamjet/agent/worker/JavaToolWorker.javajamjet-agent/src/test/java/dev/jamjet/agent/AgentIrCompilerTest.javajamjet-agent/src/test/java/dev/jamjet/agent/AgentIrParityTest.javajamjet-agent/src/test/java/dev/jamjet/agent/AgentRunDurableTest.javajamjet-agent/src/test/java/dev/jamjet/agent/TestTools.javajamjet-agent/src/test/java/dev/jamjet/agent/client/JamjetEngineClientTest.javajamjet-agent/src/test/java/dev/jamjet/agent/examples/CalculatorAgentExample.javajamjet-agent/src/test/java/dev/jamjet/agent/examples/CalculatorAgentExampleTest.javajamjet-agent/src/test/java/dev/jamjet/agent/examples/CalculatorTools.javajamjet-agent/src/test/java/dev/jamjet/agent/examples/README.mdjamjet-agent/src/test/java/dev/jamjet/agent/tools/ToolDispatcherTest.javajamjet-agent/src/test/java/dev/jamjet/agent/tools/ToolRegistryTest.javajamjet-agent/src/test/java/dev/jamjet/agent/worker/JavaToolWorkerTest.javajamjet-agent/src/test/resources/golden/agent_loop_ir.python.jsonjamjet-runtime-core/src/main/java/dev/jamjet/runtime/core/QueueType.javajamjet-runtime-core/src/main/java/dev/jamjet/runtime/core/ir/NodeKind.javajamjet-runtime-core/src/test/java/dev/jamjet/runtime/core/ir/NodeKindSerializationTest.javapom.xml
| <dependency> | ||
| <groupId>com.fasterxml.jackson.core</groupId> | ||
| <artifactId>jackson-databind</artifactId> | ||
| </dependency> |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Jackson version declarations and imports:"
rg -n --glob 'pom.xml' 'jackson-databind|jackson-bom|jackson.version'
echo
echo "POM context around Jackson-related entries:"
fd -a '^pom\.xml$' . -x sh -c '
if rg -q "jackson-databind|jackson-bom|jackson.version" "$1"; then
echo "=== $1 ==="
nl -ba "$1" | sed -n "/jackson/,+20p"
fi
' sh {}Repository: jamjet-labs/jamjet-runtime-java
Length of output: 282
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Top-level files:"
git ls-files | sed -n '1,200p'
echo
echo "POM files:"
git ls-files '*pom.xml'
echo
echo "Jackson-related matches across tracked POMs:"
rg -n --glob '**/pom.xml' 'jackson-databind|jackson-bom|jackson.version|jackson-core|jackson-annotations' .
echo
echo "jamjet-agent/pom.xml context:"
nl -ba jamjet-agent/pom.xml | sed -n '1,120p'
echo
echo "Parent POM candidates:"
for f in $(git ls-files '*pom.xml'); do
if grep -q '<packaging>pom</packaging>' "$f" 2>/dev/null; then
echo "=== $f ==="
nl -ba "$f" | sed -n '1,220p'
fi
doneRepository: jamjet-labs/jamjet-runtime-java
Length of output: 16692
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Root pom.xml Jackson section:"
python3 - <<'PY'
from pathlib import Path
p = Path("pom.xml")
for i, line in enumerate(p.read_text().splitlines(), 1):
if 55 <= i <= 85:
print(f"{i}: {line}")
PY
echo
echo "jamjet-agent/pom.xml around the dependency:"
python3 - <<'PY'
from pathlib import Path
p = Path("jamjet-agent/pom.xml")
for i, line in enumerate(p.read_text().splitlines(), 1):
if 25 <= i <= 45:
print(f"{i}: {line}")
PYRepository: jamjet-labs/jamjet-runtime-java
Length of output: 2447
🌐 Web query:
Jackson databind 2.17.2 advisories CVE high severity 2025
💡 Result:
As of June 28, 2026, Jackson Databind 2.17.2 is affected by several high-severity security vulnerabilities discovered in 2025 and 2026. Because 2.17.2 is no longer the latest maintained version, it remains vulnerable to these issues, and users are strongly advised to upgrade to a patched version (such as 2.18.8 or higher) [1][2][3][4]. Key vulnerabilities affecting version 2.17.2 include: 1. PolymorphicTypeValidator (PTV) Bypass (CVE-2026-54512): This high-severity vulnerability allows an attacker to bypass the PTV allow-list by using generic type parameters (e.g., ArrayList<com.evil.Gadget>), potentially leading to arbitrary class instantiation and remote code execution [1][2][5]. It affects all versions from 2.10.0 up to 2.18.7 [1][5]. 2. Array Subtype Allowlist Bypass (CVE-2026-54513): This issue exists in the BasicPolymorphicTypeValidator's allowIfSubTypeIsArray method, which fails to validate the component type of an array, allowing an attacker to instantiate non-allowlisted types [3]. It affects versions 2.10.0 through 2.18.7 [3]. 3. SSRF via InetSocketAddress (CVE-2026-54514): Deserializing InetSocketAddress objects triggers eager DNS resolution, which can be exploited for Server-Side Request Forgery (SSRF) or internal-resolver probing. This affects versions 2.0.0 through 2.18.7 [4]. Note on CVE-2025-52999: While widely reported as a high-severity denial-of-service vulnerability affecting jackson-core, it primarily impacts versions prior to 2.15.0 [6][7]. Jackson Databind 2.17.2 uses a more recent jackson-core and is not directly vulnerable to this specific stack-overflow issue [6]. To remediate these risks, you should update your project dependencies to use Jackson Databind 2.18.8 or a newer release [1][2][3][4].
Citations:
- 1: https://nvd.nist.gov/vuln/detail/CVE-2026-54512
- 2: GHSA-j3rv-43j4-c7qm
- 3: GHSA-rmj7-2vxq-3g9f
- 4: GHSA-hgj6-7826-r7m5
- 5: GHSA-j3rv-43j4-c7qm
- 6: https://nvd.nist.gov/vuln/detail/CVE-2025-52999
- 7: https://www.herodevs.com/blog-posts/cve-2025-52999-denial-of-service-via-stack-overflow-in-jackson-core
Bump the parent Jackson baseline. jamjet-agent/pom.xml inherits jackson-databind from the root BOM, and pom.xml:61-79 still pins jackson.version to 2.17.2. Upgrade that shared version to a patched release (for example 2.18.8+) so this module and the rest of the build stop pulling the vulnerable databind line.
🤖 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 `@jamjet-agent/pom.xml` around lines 33 - 36, The shared Jackson baseline is
still pinned to an older vulnerable databind release, so update the root
`jackson.version` property in the BOM that `jamjet-agent` inherits from to a
patched version such as `2.18.8` or newer. Make the change in the shared Maven
property, not in the `jackson-databind` dependency declaration itself, so all
modules using the BOM pick up the safer version consistently.
Source: Linters/SAST tools
Correctness (with tests): - ToolRegistry: reject overloaded @tool methods that collide on the class+method dispatch key, which would silently corrupt byClassAndMethod - JavaToolWorker: an interrupt mid-dispatch is a reclaimable no-op, not a tool failure; restore the interrupt and stop without failing the item - ToolDispatcher: a tool call missing a required arg yields a clean tool error surfaced to the model, not a null-coerced invocation Input validation: - Agent.Builder: reject non-positive timeoutSeconds and a blank model - Budget: reject an entirely-empty budget and a non-finite costUsd - RunOptions: reject a zero or negative timeout Hygiene: - NodeKind.Model: deep-copy and freeze tool schemas, not just the outer list - JamjetEngineClient.heartbeatWorkItem: accept a nullable Long lease fence and include it in the body only when present - ToolInvocationException: keep the surfaced message generic and retain the cause as the exception cause, never embedding raw cause.toString - CalculatorTools example: BigDecimal arithmetic with a clean divide-by-zero error and whole-number rendering without a trailing .0 Adds regression coverage for older Model JSON with no tools field. Full jamjet-agent and jamjet-runtime-core suites green (Java 21, 163 tests).
What
Track 9 Phase B: first-class Java authoring of a governed, durable agent. A new
jamjet-agentmodule gives a JVM developer a fluentAgentbuilder (model +@Tool+ instructions + governance) that compiles to the SAME agent-loop IR the Python ADK builds and runs on the Rust engine over HTTP, with Java@Toolmethods executing durably exactly-once via a net-new Java tool-worker draining thejava_toolqueue.dev.jamjet.agent.Agentbuilder +@Toolannotation + aToolRegistry(OpenAI-format tool schemas).agent.runDurable(prompt)compiles the IR, submits it, polls to a terminal state, and extracts the answer into anAgentResult.AgentIrCompileremits the static-unroll agent-loopWorkflowIr(Model with tool schemas, a tool-gate condition, aJavaFntool node, looping to the next Model) withstart_nodealways a Model node. Governance knobs compile into the IRpolicy/token_budget/cost_budget_usd/data_policyfields the engine enforces.JavaToolWorkerdrainsjava_tool: claim, heartbeat (threading the lease fence), resolve the@Toolvia the registry, invoke it, and complete with the lease fence.jamjet-runtime-coregains aNodeKind.JavaFn+QueueType.JAVA_TOOL(matching the engine), atoolsfield on the Model node, and a serde-leak fix.CalculatorAgentExampleshows the idiomatic Java authoring and runs the full governed durable loop end to end.The model side is free: a Java agent emits Model nodes and the engine routes model calls through the governed Python sidecar, so a Java agent inherits the same budget / policy / PII / audit governance as Python.
Engine parity
The Java-emitted IR is proven against a Python-generated golden IR: same node graph, edges, gate condition, Model tool schemas (byte-identical), governance fields, and timeouts, with the only intended difference being
java_fnvspython_fntool nodes. So a Java agent runs the same graph the engine already runs for Python.Safety
The durable worker is enforcement-adjacent, so it got a dedicated adversarial review plus the whole-branch review. Both confirmed: a stale or reclaimed worker cannot duplicate a completion (it completes with the lease fence, which the engine rejects with a 409 it treats as lost-lease, never failing the reclaimed item; a lost heartbeat aborts the in-flight tool); and tool dispatch is gated to declared
@Toolmethods through the registry, neverClass.forNameon a payload field, so a forged tool name cannot invoke arbitrary code. The whole-branch review also cross-checked the transport bodies and the result extraction against the engine routes and the Python client, and confirmed governance is not silently dropped.Tests
mvn verifygreen:jamjet-runtime-core83 tests,jamjet-agent57 tests (the WireMock transport round-trip, the golden IR parity, the dispatcher and worker adversarial cases, runDurable extraction, and the end-to-end example), 0 failures.Follow-ups
Phase C (the Spring Boot starter wrapping this, plus the
@ConditionalOnClassfix and CI for jamjet-spring). Minor: litellm bare-name parity, a fenced/failendpoint (shared with the Python worker).Summary by CodeRabbit
New Features
Bug Fixes