Eval observability embed spike - #300
Open
vishesh-orkes wants to merge 75 commits into
Open
Conversation
- Python SDK: CorrectnessEval posts EvalSuiteResult to server after each run;
EvalSuiteResult/EvalCaseResult/EvalCheckResult gain to_dict() serialization,
prompt/output capture per case, strategy/ran_by metadata, name field
- Server: new eval storage layer (schema-eval.sql + schema-eval-postgres.sql)
with eval_runs/eval_cases/eval_checks/eval_datasets tables; EvalController
exposes POST/GET /api/eval/runs, /api/eval/runs/{id}, /api/eval/datasets
and GET /api/eval/datasets/{name}; EvalService persists with @transactional
- Server: AgentService filters eval runs from production search by default;
isEvalRun() handles both JSON and Conductor Map.toString() serialization
formats; includeEvalRuns param restores them when requested
- UI: new Experiments section in sidebar with Eval Runs and Datasets pages;
EvalRunsList shows pass-rate progress bar, Cases pass/fail badges, stats row,
and search/filter bar; EvalRunDetail shows prompt, agent output, semantic score
card, strategy/ran_by metadata; DatasetsList/DatasetDetail show read-only
dataset cases
- UI: Agent Executions table gains Type column (Production/Eval chips) and
"Show eval runs" toggle next to "Hide sub-agent executions"; eval rows shown
at reduced opacity; footer links to Experiments → Eval Runs
- Tests: 19 Python unit tests (no LLM), EvalServiceTest for server persistence
… to avoid event-loop-closed error
Adds an optional OCG (Open Context Graph) retrieval sub-agent that the
main agent's LLM can delegate to when it decides it needs context. Whole
feature is gated on agentspan.ocg.url; unset means every OCG bean stays
out of the context and no behavior changes.
- 7 OCG_* WorkflowSystemTask beans (OcgRequestTask) — one per OCG
endpoint (query, get_entity, neighborhood, code_history,
memory_set/reinforce/delete). Each proxies a single HTTP call with
field projection + response capping (response-cap-chars, default
8192) so a large graph traversal can't blow the model's context.
- _ocg_agent workflow registered at startup by OcgSubAgentService —
a normal AgentConfig built by OcgAgentFactory with the OCG system
prompt and the seven ocg_* tools.
- OcgAgentToolInjector silently appends an ocg_agent agent_tool to
every top-level AgentConfig at compile time (skips self-injection
on _ocg_agent and duplicate injection if a user already declared
it). Main agent's LLM sees it as a peer it can call; tool call
dispatches SUB_WORKFLOW(_ocg_agent) which runs its own LLM ↔
ocg_* tool loop and returns a synthesized answer.
- ToolCompiler TYPE_MAP + enrichment script (both static and
dynamic variants) get an OCG bucket so ocg_* tools route to the
right OCG_* task type at runtime.
Wires OCG_API_KEY env → agentspan.ocg.api-key → Authorization: Bearer header on every OCG_* system task's HTTP request. Empty key keeps the header off so unauthenticated local OCG instances still work.
Two intertwined changes that ended up in one commit because they touch
the same files:
1) Fix OCG end-to-end dispatch (was broken on the previous tip).
- Register a TaskDef for each ocg_* tool name in OcgSubAgentService.
Conductor resolves dynamic-fork tasks by name in the TaskDef
registry; without the def, dispatch failed with
"Cannot find task by name ocg_query in the task definitions".
- Prefix every OCG endpoint with /api/v1 (was hitting
/agent/query etc., the real paths are /api/v1/agent/query etc.).
The OCG service returned grpc-gateway 404 NOT_FOUND on every call.
With both fixes the full chain works: main LLM → ocg_agent
SUB_WORKFLOW → ocg_query OCG_QUERY task → POST /api/v1/agent/query
→ real citations back.
2) Replace the OCG-specific Injector with a generic compiler hook.
- AgentCompiler picks up any WorkflowDef whose metadata carries the
agentspan.autoExposeAsTool flag and appends it as an agent_tool
on every top-level compile. Self-recursion + duplicate guards live
inside the merger. Optional @Autowired MetadataDAO keeps existing
`new AgentCompiler()` test paths working unchanged.
- OcgSubAgentService stamps the flag on _ocg_agent's metadata at
startup. That's the *only* line that ties OCG to LLM visibility
— everything else is generic.
- OcgAgentToolInjector + its 5-test pinning class are deleted.
AutoExposedToolsMergeTest (6 tests) replaces them and is
deliberately not OCG-specific, so future server-side sub-agents
rely on the same contract.
- AgentService loses its @Autowired OcgSubAgentService field and
the maybeInjectOcgAgentTool helper. resolveConfig is back to a
plain normalize-or-passthrough.
Future server-side sub-agents (`_foo_agent`, `_bar_agent`, …) now drop
in by stamping the same metadata flag and registering their workflow.
No AgentCompiler change, no AgentService change, no per-feature
injection class.
Replace the switch-statement-heavy OcgRequestTask with a per-endpoint
strategy. Each operation owns its URL/method/body/projection in its own
class; the task itself is a thin orchestrator.
- OcgOperation interface: taskType(), name(), build(), project()
- Seven concrete operations under runtime/ocg/operation/ — one per
endpoint (query, get_entity, neighborhood, code_history,
memory_{set,reinforce,delete})
- Three shared utilities in the same sub-package:
OcgInputs — pick/required/intOrDefault/parseJsonLenient/writeJson
OcgUri — UriComponentsBuilder rooted at /api/v1
OcgRequest — HttpRequest factory: base() / postJson() / get() / delete()
- Apache Commons replaces hand-rolled helpers:
StringUtils.removeEnd → trim trailing slash
StringUtils.abbreviate → response cap (with custom marker) and
log-body truncation
Validate.isTrue → required-input check
NumberUtils.toInt → string-to-int fallback
- Spring UriComponentsBuilder handles URL encoding properly. One
behavioural improvement: query params no longer over-encode ':' to
'%3A' (it's not reserved in RFC 3986 query values). Updated the
memoryDelete test assertion accordingly.
- OcgRequestTask is now ~100 lines total. Largest method is 16 lines;
every other method is under 10. No switch statements anywhere.
- OcgRequestTaskConfig becomes one @bean per operation, each pairing
a fresh OcgRequestTask with its strategy.
End-to-end smoke test on the live OCG dev instance still works — same
shape, same citations, same token count.
Replace OCG's bespoke @PostConstruct registration with a generic two-bean registry that any future server-side sub-agent can plug into without writing per-feature service code. Generic infrastructure (runtime/registry/, OCG-agnostic): - RegisteredAgent — interface; agentConfig() + autoExpose() - RegisteredAgentRegistrar — @PostConstruct picks up every bean, compiles via AgentCompiler, stamps the auto-expose marker when requested, and writes to MetadataDAO - RegisteredTaskDefs — interface; taskDefs() - RegisteredTaskDefsRegistrar — runs first via @dependsOn OCG plug-in (just two @components — no @configuration wrapper): - OcgRegisteredAgent — implements RegisteredAgent - OcgRegisteredTaskDefs — implements RegisteredTaskDefs - OcgSubAgentService — DELETED (responsibilities moved to the two registrars; no per-feature @PostConstruct anywhere now) Both @components carry @ConditionalOnExpression rather than @ConditionalOnProperty: the latter treats empty strings as "present and not false", so empty OCG_URL would still instantiate the beans and leak the auto-expose registration into the test DB. The same conditional is now on OcgRequestTaskConfig for consistency. Two new test classes (6 tests total) pin the contract every future sub-agent relies on: compile, stamp-if-exposed, persist; no stamp when autoExpose() returns null; empty supplier lists are no-ops. End-to-end OCG smoke test on the live dev instance still works identically — same shape, same citations, same token count.
The method was a 60-line block mashing six concerns: DAO fetch with
try/catch, existing-name collection, per-workflow metadata parsing,
self/duplicate guards, ToolConfig construction, and write-back.
Split into:
- mergeAutoExposedTools — the loop, ~15 lines, zero control-flow
keywords inside the body (no continue/break). Reads as: "fetch
defs, collect taken names, for each def Optional.ifPresent build,
commit if anything was added."
- tryBuildAgentTool — per-workflow filter chain with named guard
clauses; returns Optional<ToolConfig>
- safelyFetchAllWorkflowDefs — the DAO call + warn-on-failure
- collectToolNames — existing-name accumulation
- readAutoExposeSpec — metadata parsing into a typed AutoExposeSpec
record
- buildAgentTool — ToolConfig construction
- appendTools — copy-on-write list mutation
The two-pass "contains then add" dedup collapses into a single
takenNames.add(...) check that does both, halving the guard lines.
Behavior unchanged — 714 tests still green.
The OCG sub-agent's LLM was hallucinating date ranges
(e.g. picking 2023-10-10 as end_time when "today" is 2026-06-09),
filtering out fresh data and producing thin synthesis.
Anchor the LLM on a real date:
- Add TODAY_PLACEHOLDER ({{TODAY}}) to OCG_SYSTEM_PROMPT
- OcgAgentFactory.build() replaces it with LocalDate.now(UTC)
at workflow-compile time
- Prompt opens with "Today's date is <today> (UTC)" and explicit
guidance to anchor relative ranges on it and omit ranges when
none are implied
Refreshes on every server restart since RegisteredAgentRegistrar
recompiles _ocg_agent at startup. Long-running servers will drift —
fix can move to a runtime-resolved template var later if that matters.
The CI guard 'checkNoInlineFQN' caught the inline java.time.LocalDate.now(java.time.ZoneOffset.UTC) call I added when baking today's date into the OCG system prompt.
…, lazy cache Three concerns from the OCG branch code review: 1. mergeAutoExposedTools used to fire on every recursive compile because compileSubAgent (and the graph-structure subgraph compile, and MultiAgentCompiler's swarm inner-workflow compile) all called back into the public compile() entry. The Javadoc claimed top-level-only — the code contradicted it. Nested specialist sub-agents silently inherited ocg_agent and the DAO got re-queried per nesting level. Public compile() now runs the merge then delegates to a new package-private compileWithoutAutoExpose() that does the strategy dispatch + post-processing. The three internal recursion sites switch to the non-merging entry. Pinned by mergeRunsOnceAtTopLevelOnlyAndSkipsInternalRecursion. 2. MetadataDAO was field-injected with @Autowired(required = false), forcing AutoExposedToolsMergeTest to use ReflectionTestUtils. Switched to constructor injection with a no-arg overload that preserves existing `new AgentCompiler()` call sites; AutoExposedToolsMergeTest setUp now uses `new AgentCompiler(metadataDAO)`. 3. safelyFetchAllWorkflowDefs hit the DAO on every compile. Registered server-side agents are written at @PostConstruct and don't change at runtime, so the per-request fetch was wasted work. Added a volatile List<AutoExposedEntry> cache with double-checked locking: successes cached for the lifetime of the bean, transient DAO failures left uncached so the next compile retries (matches the existing "merge is a convenience, not a correctness requirement" contract). Three new tests in AutoExposedToolsMergeTest pin the contracts. Each was verified failing against the broken state before landing the fix. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Smaller review items from the OCG branch:
- OcgRequestTask.start used to catch Exception, which swallowed
InterruptedException without re-flagging the current thread. A task
cancelled mid-http.send would appear to "fail" silently and Conductor's
executor would never observe the cancellation. Now catches
InterruptedException separately and calls Thread.currentThread().interrupt()
before failing. The remaining catch is IOException | RuntimeException so
Error still propagates.
- OcgRequestTask.send/complete declared `throws Exception`; tightened to
the actual checked exceptions. OcgOperation.build is also tightened from
`throws Exception` to `throws IOException` (JsonProcessingException
extends IOException, so the postJson-using operations still compile).
- Two missing OcgRequestTaskTest cases for the memory_set and
memory_reinforce body shapes: memory_set must strip the server-side
__agentspan_ctx__ glob before forwarding; memory_reinforce must only
forward the four picked fields (no key, no __agentspan_ctx__, no rogue
fields the LLM might attach). Tests read the actual HttpRequest body via
a Flow.Subscriber helper.
- OcgAgentFactoryTest now pins the {{TODAY}} substitution and the absence
of the literal placeholder in the rendered prompt.
Each new test was verified failing against an intentionally broken state
before landing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…he at boot `RegisteredAgentRegistrar.register()` was calling `agentCompiler.compile()`, which triggers the auto-expose merger's lazy DAO scan. During the registrar's own `@PostConstruct` loop the to-be-registered agent isn't yet in the DAO, so the merger cached an empty list and froze it for the bean's lifetime — every user compile post-startup saw no auto-exposed tools and the OCG sub-agent was silently invisible to every LLM. End-to-end smoke against a live server confirmed the bug, then confirmed the fix. - Promote `AgentCompiler.compileWithoutAutoExpose` to public; registrar now calls it so bootstrap never touches the merger cache. - Extract `AutoExposedToolsMerger` (was ~140 lines inside `AgentCompiler`) as its own `@Component`. AgentCompiler keeps a thin `mergeAutoExposedTools` delegate for the existing test API. - Add `RegisteredAgentBootstrapTest` — stateful in-memory `MetadataDAO` exercises register → user-merge ordering. Verified failing on pre-fix code, passing post-fix. - Update `RegisteredAgentRegistrarTest` mocks to track the new entry point. - Drop duplicate `agentspan.ocg.response-cap-chars` default from `application.properties` (already in `OcgProperties`). - Simplify `OcgMemoryDeleteOperation.addQueryParamIfPresent`: drop the redundant `StringUtils.defaultString` wrapping. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CI flagged a handful of line-wrap differences in files touched by the previous fix. Local palantir-java-format 2.50.0 + Zulu JDK 21 throws NoSuchMethodError so spotlessApply is unavailable here; applying the hunks reported in the CI build log by hand. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Last hunk CI flagged after the prior fixup landed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drop the silent ``openai/gpt-4o-mini`` default for ``agentspan.ocg.model``. The right model depends on cost, latency, and the OCG corpus, so it has to be an explicit operator decision rather than an inherited fallback. ``OcgAgentFactory.build`` now throws ``IllegalArgumentException`` with an operator-actionable message when ``OCG_URL`` is set but ``OCG_MODEL`` is blank, so boot fails fast instead of silently routing OCG traffic through the wrong model. ``OCG_MODEL`` is documented as required (alongside ``OCG_URL``) in the docs setup table; the optional-knobs table no longer lists it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ans, not a DAO scan AutoExposedToolsMerger previously discovered auto-exposed sub-agents by scanning the metadata store for WorkflowDefs stamped with a metadata marker, caching the result forever. The data it was reading back came from the same Spring context: the registrar wrote RegisteredAgent beans into the DAO, then the merger scanned them back out. That round-trip is what created the bootstrap-ordering trap (a compile during @PostConstruct froze an empty cache for the bean's lifetime, silently hiding every registered agent — see 879851f) plus the lazy cache, the transient-failure retry carve-out, and the untyped metadata parsing. Now the merger builds its entries from List<RegisteredAgent> at construction, so it is complete before any compile can run and the trap is structurally impossible. Deleted along the way: - the AUTO_EXPOSE_AS_TOOL_METADATA_KEY wire protocol (stamping in the registrar, parsing in the merger, the re-export on AgentCompiler) - the volatile cache + double-checked locking + failure-retry logic - AgentCompiler's MetadataDAO constructor and mergeAutoExposedTools delegate The registrar still persists each WorkflowDef — that's needed for SUB_WORKFLOW dispatch by name — but it's now a dumb compile-and-persist loop. A blank autoExpose tool name now fails fast at boot instead of being silently skipped. Tests rewritten to the bean-list model; the obsolete cache-behavior tests are replaced by construction-time pins. Validated per CLAUDE.md by mutating merge() into a no-op: exactly the injection-asserting tests failed, guards stayed green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…time
OcgAgentFactory baked LocalDate.now() into the registered system prompt,
so the date anchor for "recent" / relative-range queries was the server's
boot date. On a long-running server the prompt drifts until it claims
last week (or last month) is "today", and the LLM bounds every relative
query against the wrong anchor — the exact hallucinated-date failure the
anchor was added to prevent.
Now the agent_tool dispatch script (which runs per execution) injects
__today__ = current UTC date into every sub-workflow's input, and the
OCG prompt references ${workflow.input.__today__}, substituted by
Conductor when the LLM task is scheduled. The injection is generic:
any future sub-agent prompt can use the same input.
Tests written first and confirmed red before the fix, per CLAUDE.md:
- OcgAgentFactoryTest pins the prompt to the runtime expression
- EnrichToolsScriptTest executes the real dispatch script in GraalJS and
asserts the SUB_WORKFLOW input carries a yyyy-MM-dd __today__
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Observed in execution 24fd619d (2026-06-11): asked to "catch me up on
the current state", the sub-agent issued an ocg_query with
end_time=2026-06-01 — ten days before today — silently dropping the
newest data. The __today__ runtime anchor was working (the substituted
prompt read "Today's date is 2026-06-11"); the model still closed the
window early because (a) the prompt's example showed a hardcoded
month-shaped window ending before today, which the model imitated, and
(b) nothing said what end_time should be for open-ended questions.
Prompt changes:
- Explicit rule: ranges extending to the present ("recent", "current
state", "catch me up") set start_time and OMIT end_time; end_time is
only for windows that closed in the past.
- The example's literal dates are gone — replaced with a relative
"<today minus 30 days>" start and no end_time — so there are no
stale calendar dates in the prompt for the model to anchor on.
Test written first and confirmed red, per CLAUDE.md: pins the omit-rule
text and asserts the prompt contains no literal yyyy-MM-dd dates.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ystem_Task # Conflicts: # server/conductor-agentspan-server/src/test/java/dev/agentspan/runtime/compiler/AutoExposedToolsMergeTest.java # server/conductor-agentspan-server/src/test/java/dev/agentspan/runtime/compiler/RegisteredAgentBootstrapTest.java # server/conductor-agentspan/src/main/java/dev/agentspan/runtime/compiler/AutoExposedToolsMerger.java
Rebased onto main after the credentials->secrets rename and the server split (#271): server changes now live in conductor-agentspan, and the Java SDK changes target the org.conductoross.conductor.ai.model package. AgentHumanTask.start() and AgentService.getStatus() now read the compiler-emitted tool_calls array (${llm.output.toolCalls}), normalise each entry to {name, args}, and emit it as pendingTool.toolCalls. Legacy singular tool_name/parameters keys are preserved (null) for back-compat. Args precedence now matches the rest of the runtime (JavaScriptBuilder: tc.inputParameters || tc.input): inputParameters is canonical, input is the fallback. Server tests use the canonical inputParameters shape and add explicit precedence coverage. Java/TS SDKs gain typed PendingToolCall views; Python/C# inherit via the raw pendingTool map. Closes #226
…ks (#272) * refactor(server): split into conductor-agentspan library + thin server; drop auth stack Invert the AgentSpan/Conductor dependency so AgentSpan is a library Conductor can depend on, with a thin standalone server over it. - Two Gradle modules: conductor-agentspan (library) and conductor-agentspan-server (OSS runtime + bootJar). Old single-module src/ removed. - Conductor artifacts are compileOnly in the library so the host supplies the engine version; the server brings the runnable OSS Conductor. - Pin com.networknt:json-schema-validator to 1.0.73 — conductor-ai -> spring-ai drags in 2.0.0, which dropped com.networknt.schema.JsonSchema and broke conductor-common's JsonSchemaValidator bean (context-refresh hang on startup). - Remove the unused auth/user enforcement stack (UserRepository, ApiKeyRepository, AuthController, AuthUserSeeder, AuthProperties) and the users/api_keys tables; auth was off by default and never enforced. - Collapse the request principal to a String userId, delete User, and relocate the carrier from auth/ to a context/ package; AuthFilter sets an anonymous userId. - Add design docs under server/docs/. Full build green: 666 tests, 0 failures. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(python-sdk): tokenize CLI command so full command lines work The run_command tool (cli_allowed_commands) assumed `command` was the bare executable, but LLMs routinely pass the whole command line ("gh repo list --limit 5"). os.path.basename then returned the entire string, failing the whitelist check ("Command 'gh repo list ...' is not allowed"), and non-shell exec tried to run a binary named after the full line. Tokenize with shlex: validate on the executable, exec tokens + args. Adds tests covering the full-command-line case. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(ui): rename Credentials sidebar entry to Secrets Match the server-side secrets rename (/api/secrets). Label and id updated; it already links to SECRETS_URL. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(ui): remove dead secrets login gate; rebuild bundle for /api/secrets AuthController and the auth-enforcement stack were removed server-side (OSS runs anonymous; an embedding host like orkes supplies its own auth), so the secrets page's login flow is obsolete and pointed at the deleted /api/auth/login. - Drop LoginDialog, useSecretAuth, and useLogin (and the LoginRequest/Response types and their tests); SecretsPage runs anonymously with no token. - Rebuild the served UI bundle so the browser stops calling the old /api/credentials endpoint and uses /api/secrets/v2. Verified: 0 /auth/login and 0 /credentials fetch paths in the new bundle. UI tests: 455 passed, 1 skipped. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(server): extract storage/secret SPIs to library, push impls to server Phase 1 of the library split: conductor-agentspan now defines the SPI contracts + logic; the concrete implementations live in conductor-agentspan-server, so an embedding host (orkes) can supply its own. - New dev.agentspan.runtime.spi package in the library: CredentialStoreProvider, SkillPackageStore (+ StoredSkillPackage), and a new SecretOutputMasker. - Impls + infra moved to the server module: EncryptedDbCredentialStoreProvider, FileSystem/ConductorPayload skill stores, MasterKeyConfig, CredentialDataSourceConfig, CredentialSchemaMigrator, CredentialEnvSeeder, schema-credentials*.sql, and the no-op masker (CredentialOutputMasker -> NoOpSecretOutputMasker). - Library logic (CredentialResolutionService, SkillRegistryService, CredentialMaskingResponseAdvice) depends only on the SPI interfaces; verified the library main references no concrete impl. - Removed SkillRegistryService's convenience constructor that instantiated FileSystemSkillPackageStore; tests pass a store explicitly. Decisions: ExecutionTokenService stays in the library (internal token protocol, not a swappable backend); credential DataSource keeps @Primary (needed standalone; embedding-time change deferred to Phase 4). Wiring still flows through @ComponentScan; Phase 2 converts to auto-config with @ConditionalOnMissingBean. Full build green: 666 tests, 0 failures. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(server): self-register the library via Spring auto-configuration Phase 2 of the library split: conductor-agentspan now registers its beans through Spring Boot auto-configuration, so an embedding host (orkes) gets them without adding dev.agentspan to its own component scan. - New AgentSpanAutoConfiguration (@AutoConfiguration) component-scans dev.agentspan.runtime (excluding AgentRuntime and itself), exported via META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. - AgentRuntime now scans only the Conductor packages; AgentSpan beans come from the auto-config. No double-scan. - Split-package design makes the scan register library beans always and server-extra beans (default SPI impls, web config) only when present — so standalone gets everything and an embedding host supplies its own SPI impls. Phase 3 verification: full build green (666 tests, 0 failures); the standalone bootJar boots in ~3s and serves (/api/agent/list 200, /api/secrets/v2 200, /actuator/health 200), confirming the auto-config imports file is read from the nested library jar in the fat jar. Deferred to Phase 4 (embedding): converting the @Primary task/listener/datasource overrides to opt-in. They only conflict when embedded in orkes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * build(server): publish modules to Maven Central; tidy library deps Publish conductor-agentspan and conductor-agentspan-server to Maven Central under the org.conductoross.conductor group, mirroring the Java SDK's setup. - Add com.vanniktech.maven.publish (apply-false at root, applied per module) with full POM metadata, coordinates org.conductoross.conductor:<module>:<version>, and conditional signing. Group/version set on subprojects; version from gradle.properties (default 0.1.0) or -Pversion=X in CI. - Library jar publishes as conductor-agentspan(.jar); server's plain jar as conductor-agentspan-server(.jar) without the "-plain" classifier. The runnable fat jar (agentspan-runtime.jar) is still released via the S3/GitHub workflow. - New release-server-maven.yml: workflow_dispatch with version, maven-central environment, publishAndReleaseToMavenCentral (publishes both modules), reusing the SONATYPE_*/SIGNING_* secrets. - Javadoc made lenient (Xdoclint:none + failOnError=false) so Lombok's onConstructor_ doesn't abort the Central-required javadoc jar. Dependency hygiene for the published library artifact: - Drop dead impl-layer deps from the library (spring-jdbc, HikariCP, sqlite-jdbc, spring-security-crypto) — moved to the server with the SPI impls in Phase 1 / removed with the auth stack. - Library exports only the slf4j facade (not a log4j2 binding) and no springdoc; both are runtime/app concerns now declared explicitly in the server. Verified: clean build green (666 tests); publishToMavenLocal produces correct coordinates/POM/sources/javadoc for both modules; bootJar boots and serves /api-docs, /api/agent/list, /api/secrets/v2 (all 200). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(java-sdk): migrate to Conductor client, fix e2e degradation, add framework coverage ## Core changes - Migrate worker layer to official conductor-client 5.0.1 (TaskRunnerConfigurer + Worker with lease-extend heartbeat); delete hand-rolled WorkerHttp - Use io.orkes ApiClient with native key/secret auth; remove serverUrl/authKey/authSecret from AgentConfig (they don't belong there) - Move all public/internal classes to org.conductoross.conductor.ai / .internal packages - Add AgentClient (peer of WorkflowClient) for /api/agent/* control-plane - Add SseClient riding ApiClient.buildCall for SSE streaming - Remove HttpApi.java entirely; all HTTP rides the conductor client ## e2e bug fixes - Fix stateful-domain worker bug: domain change on re-registration now triggers runner rebuild so workers poll the correct per-execution queue (not the default) - Remove redundant no-domain prepareWorkers call from runAsync/streamAsync that caused the domain-aware registration to be skipped ## e2e performance fixes (3h → 2m 44s) - Fix MIN_WORKER_THREADS=16 ignoring configured threadCount; use MIN_THREADS_PER_WORKER=1 so AgentConfig(100,1) gets 1 thread, not 16 (was 160 req/s to SQLite server) - Add connectTimeout/readTimeout/writeTimeout to default ApiClient construction (was infinite; slow server responses blocked forever) - waitForResult: fail after 10 consecutive errors with root cause in message instead of silently spinning to 600s timeout - AgentRuntime.close() now calls conductorClient.shutdown() to evict OkHttp pool - maxParallelForks=3 for e2e task: I/O-bound suites with unique names run concurrently ## Test coverage - 204 unit tests (up from 39), 0 failures - 340 e2e tests across 25 suites, 0 failures, 0 skipped - New: WorkerManagerDomainTest (domain rebuild regression, proven counterfactual) - New: WorkerManagerThreadCountTest (thread formula regression, proven counterfactual) - New: AgentHandleErrorTest (fast-fail regression, @Timeout(5) proves counterfactual) - New: AdkBridgeTest (ADK serialization unit, server-free) - New: Suite11bOpenAIAgent (OpenAI framework e2e: tagging + compile + runtime) ## Formatting - Add spotless with palantirJavaFormat 2.50.0; apply to src/main, src/test, e2e Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * updates * fix(ci): correct server JAR path in e2e workflows bootJar runs in server/ working-directory, so the jar lands at conductor-agentspan-server/build/libs/ not build/libs/. Upload and download paths were pointing at the wrong location, causing all e2e jobs to fail with "Artifact not found: server-jar". Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * refactor(java-sdk/spring): remove redundant auth props; depend on conductor-client-spring AgentspanProperties had serverUrl/authKey/authSecret that duplicated what conductor-client-spring's OrkesConductorClientAutoConfiguration already handles via conductor.* properties. Removed them. AgentspanAutoConfiguration no longer creates the ApiClient bean — it takes the one wired by conductor-client-spring (after = OrkesConductorClientAutoConfiguration). Our module now only owns the two Agentspan-specific knobs: workerPollIntervalMs and workerThreadCount. Add conductor-client-spring 5.0.1 as an api dependency so users get the ApiClient auto-configuration transitively. Users configure connectivity once: conductor.root-uri=http://localhost:6767/api conductor.security.client.key-id=my-key # optional conductor.security.client.secret=my-secret # optional And Agentspan worker tuning separately: agentspan.worker-poll-interval-ms=100 agentspan.worker-thread-count=1 Also extend spotless coverage to spring/src/**/*.java. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * perf(e2e): parallelize Python and TypeScript e2e to target <5 min Python: -n 1 → -n 3 --dist=loadgroup - pytest-xdist was already installed but unused (single worker) - xdist_group markers already protect credential suites (suite2/3/4/5 serialize within "credentials" group; suite16 serializes within "cli-skills") — loadgroup enforces this correctly - 13 independent suites spread across 3 workers; ~3× wall-clock reduction TypeScript: maxForks 2 → 3 - All 23 suites use unique credential names so concurrent forks don't conflict (E2E_TS_CRED_A/B, GITHUB_TOKEN, MCP_AUTH_KEY_TS, HTTP_AUTH_KEY_TS) - Suites 17/18 (the previously-cited "heavy" ones) don't use credentials - Additional fork reduces the sequential tail per worker Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * docs(java-sdk): add mkdocs documentation for Java SDK 14 pages covering the full public API: sdk/java/docs/ ├── mkdocs.yml — standalone site config (serves independently) ├── index.md — overview, install, hello world ├── getting-started.md — setup, first agent, first tool, streaming ├── spring-boot.md — auto-configuration, properties, bean overrides ├── api-reference.md — complete method signatures for all public classes ├── concepts/ │ ├── agents.md — Agent.builder() full reference, AgentRuntime API │ ├── tools.md — @Tool, HTTP, MCP, CLI, human, PDF, media, agent tools │ ├── multi-agent.md — all 7 strategies with examples │ ├── guardrails.md — regex, LLM, custom; positions; OnFail actions │ ├── termination.md — MaxMessage, StopMessage, TextMention, TokenUsage, composition │ ├── scheduling.md — cron deploy, Schedule.builder(), Schedules API │ └── skills.md — SKILL.md format, Skill.skill(), loadSkills() └── frameworks/ ├── langchain4j.md — @Tool POJO bridge, LangChainBridge ├── openai.md — OpenAIAgent builder, handoffs, structured output └── google-adk.md — AdkBridge.toAgentspan(), agentBuilder(), mapping table docs/java-sdk → symlink to sdk/java/docs so root mkdocs.yml can include it. Root mkdocs.yml gains a "Java SDK" nav section before Reference. Standalone: cd sdk/java/docs && mkdocs serve Main site: mkdocs serve (from repo root) Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * docs(java-sdk): add AgentClient control-plane API reference Covers all 5 public methods with exact HTTP verbs, paths, and field-level input/output documentation sourced from the server's StartRequest, CompileResponse, StartResponse, AgentConfig, and ToolConfig DTOs: POST /api/agent/compile → CompileResponse (workflowDef, requiredWorkers) POST /api/agent/deploy → StartResponse (agentName, requiredWorkers) POST /api/agent/start → StartResponse (executionId, agentName, requiredWorkers) GET /api/agent/{id}/status → status, isComplete, isWaiting, pendingTool shape POST /api/agent/{id}/respond → HITL resume (204) GET /api/workflow/{id} → raw Conductor workflow (via WorkflowClient) Also documents AgentConfig and ToolConfig field tables. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * docs(java-sdk): clarify getWorkflow is post-completion enrichment, not polling Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * refactor(java-sdk): remove getWorkflow from AgentClient; use WorkflowClient directly AgentClient owns the agentspan-proprietary /api/agent/* control-plane. getWorkflow was fetching from the standard Conductor /api/workflow/* endpoint via WorkflowClient — that belongs with WorkflowClient, not AgentClient. Changes: - AgentClient: remove getWorkflow(), workflowClient field, Workflow/WorkflowClient imports - AgentHandle: inject WorkflowClient directly; call workflowClient.getWorkflow() with the typed Workflow/Task objects — eliminates the JSON roundtrip (Workflow → JSON string → Map) that the old delegation required - AgentRuntime: construct WorkflowClient alongside AgentClient; pass it to every new AgentHandle(...) Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * docs(java-sdk): add method summary table to AgentClient API reference Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * docs(java-sdk): sync AgentClient API doc with current implementation - Remove getWorkflow from AgentClient methods table (deleted in previous refactor) - Rename compile/deploy/start entries to match actual method names (compileAgent etc.) - Reclassify getWorkflow section as 'WorkflowClient usage' — it is not an AgentClient method; AgentHandle calls WorkflowClient.getWorkflow directly - Tighten AgentClient javadoc: explicit scope (five endpoints only), note that standard Conductor endpoints go through their own typed clients Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * docs(java-sdk): rewrite AgentClient API doc with structural proof Every method now shows exact field names sourced from the server DTOs and verified against how AgentRuntime/AgentHandle parse the response: compile → server CompileResponse (workflowDef, requiredWorkers) SDK: plan() returns the raw map; callers get("workflowDef") deploy → server StartResponse (agentName, requiredWorkers; no executionId) SDK: deploy() reads resp.getOrDefault("agentName", agent.getName()) start → server StartResponse (executionId, agentName, requiredWorkers) SDK: extractExecutionId() tries executionId → workflowId → id → correlationId getAgentStatus → plain Map built from live Workflow object (not a DTO) source fields documented (workflow.getStatus().name(), etc.) respond → void; SDK approve/reject/respond mapped to exact body shapes Removed incorrect "CompileResponse" class reference in the table; corrected return type to show the actual JSON shape. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * refactor(java-sdk): replace Map returns in AgentClient with typed POJOs Every AgentClient method now has a proper return type instead of Map<String,Object>: compileAgent() → CompileResponse { workflowDef, requiredWorkers } deployAgent() → StartResponse { executionId=null, agentName, requiredWorkers } startAgent() → StartResponse { executionId, agentName, requiredWorkers } getAgentStatus() → AgentStatusResponse { status, isComplete, isRunning, isWaiting, output, reasonForIncompletion, pendingTool } respond() → void (unchanged) New classes: model/CompileResponse.java — public (returned by AgentRuntime.plan()) internal/StartResponse.java — internal; @JsonAlias for legacy executionId keys internal/AgentStatusResponse.java — internal; polled by AgentHandle internal/PendingTool.java — internal; nested in AgentStatusResponse Callers updated: AgentRuntime.plan() → returns CompileResponse AgentRuntime.deploy() → reads resp.getAgentName() instead of Map.getOrDefault() AgentRuntime.startAsync() → reads resp.getExecutionId() directly; extractExecutionId() deleted AgentHandle.waitForResult / isWaiting / waitUntilWaiting / buildResult → typed getters AgentStream.waitForResult / buildResultFromStatus → typed getters Agentspan.plan() → returns CompileResponse BaseTest.getAgentDef(CompileResponse) → uses plan.getWorkflowDef() 15 e2e suites → Map<String,Object> plan = → CompileResponse plan = Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * docs(java-sdk): sync AgentClient API doc with POJO refactor Update all stale references from the Map<String,Object> era: - Methods table now shows Java return types (CompileResponse, StartResponse, AgentStatusResponse, void) instead of JSON shape strings - compileAgent: response section uses CompileResponse getters, not plan.get() - deployAgent: uses resp.getAgentName() not Map.getOrDefault() - startAgent: extractExecutionId() reference removed (deleted); shows StartResponse.getExecutionId() + @JsonAlias legacy key handling - getAgentStatus: response described as AgentStatusResponse (typed POJO), not 'plain Map<String,Object>'; PendingTool fields added with getters - respond: return type shown as void, not '204 No Content' - WorkflowClient section: notes Workflow/Task typed objects, not Map walk Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * refactor(java-sdk): type AgentClient input Maps as AgentRequest and RespondBody AgentClient no longer takes Map<String,Object> as input: compileAgent(AgentRequest) — was Map<String,Object> deployAgent(AgentRequest) — was Map<String,Object> startAgent(AgentRequest) — was Map<String,Object> respond(String, RespondBody) — was (String, Map<String,Object>) New internal classes: AgentRequest — matches server StartRequest field-for-field: - agentConfig / framework + rawConfig (mutually exclusive, native vs framework) - prompt, sessionId, runId, staticPlan (@JsonProperty("static_plan")) - media, context, idempotencyKey, credentials, skillRef, timeoutSeconds - @JsonInclude(NON_NULL) — null fields omitted from wire - Factory: AgentRequest.nativeAgent(map) / frameworkAgent(fw, map) RespondBody — replaces Map<String,Object> for /respond: - RespondBody.approve() / approve(comment) / reject(reason) / of(map) - @JsonAnyGetter flattens extra fields to top level (MANUAL strategy) AgentRuntime: all three payload-building HashMap blocks replaced with agentRequest(agent, serialized).prompt(...).runId(...).build() AgentHandle, AgentStream: approveBody/rejectBody helpers deleted; all respond calls use RespondBody factories. Structural proof: AgentRequest @JsonProperty names verified against server StartRequest field names (including "static_plan" alias). docs(java-sdk): update agent-client-api.md with AgentRequest/RespondBody - Methods table shows Java input types - AgentRequest field mapping table (SDK → JSON key → server field) - RespondBody factory → wire JSON table - static_plan mismatch explained (@JsonProperty on both sides) Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * refactor(java-sdk): replace Map fields in AgentRequest with Agent and Plan types AgentRequest now holds SDK types instead of pre-serialized Maps: agentConfig: Agent (was Map<String,Object>) rawConfig: Agent (was Map<String,Object>) staticPlan: Plan (was Map<String,Object>) Serialization to the server's wire format is handled by two new Jackson JsonSerializer inner classes: AgentConfigSerializer.AsJson — applied via @JsonSerialize on agentConfig and rawConfig fields; calls serialize(agent) and writes the result so the server's AgentConfig DTO sees the correct camelCase map. Plan.AsJson — applied via @JsonSerialize on staticPlan; calls plan.toJson() so the server's PAC consumes the same format as Python/TypeScript. AgentRuntime: serialize() calls removed from plan/deploy/startAsync. The AgentConfigSerializer field and instance are gone from the runtime — serialization now happens inside AgentRequest/Jackson, not at the call site. agentRequest(Agent) no longer takes a pre-serialized Map. context: Map<String,Object> stays — intentionally free-form pass-through. docs: AgentRequest table updated with Java types and serializer column. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * refactor(java-sdk): eliminate agentConfig/rawConfig duplication; add Framework enum AgentRequest previously had two Agent-typed fields (agentConfig, rawConfig) that were mutually exclusive and duplicated the @JsonSerialize annotation. Replaced with a single agent: Agent field + Framework enum discriminator. AgentRequest.Serializer handles the key decision: framework == null → "agentConfig": serialize(agent) framework != null → "framework": fw.wireValue(), "rawConfig": serialize(agent) All JSON logic is now in one place. @JsonProperty / @JsonInclude / @JsonSerialize annotations removed from individual fields — the Serializer owns everything. Framework enum (enums package, public): OPENAI("openai"), GOOGLE_ADK("google_adk"), SKILL("skill") @JsonValue on wireValue() → serializes to the server-expected string Framework.of(String) → Optional<Framework> for safe conversion AgentRuntime.agentRequest(Agent): Framework.of(agent.getFramework()) .map(fw -> AgentRequest.frameworkAgent(fw, agent)) .orElseGet(() -> AgentRequest.nativeAgent(agent)) Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * feat(java-sdk): add missing Framework enum values to match all server normalizers Framework.of(String) already returns Optional.empty() for unknown values, so adding these is safe — existing code that sets langchain/langgraph via .framework(string) on Agent will now resolve to the typed enum instead of falling through to the native path. Added: LANGCHAIN("langchain") — LangChainNormalizer LANGGRAPH("langgraph") — LangGraphNormalizer VERCEL_AI("vercel_ai") — VercelAINormalizer CLAUDE_AGENT_SDK("claude_agent_sdk") — ClaudeAgentSdkNormalizer Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * docs(java-sdk): sync agent-client-api.md with Framework enum + AgentRequest refactors - AgentRequest section rewritten: single 'agent: Agent' field replaces the old agentConfig/rawConfig duplication; Serializer table shows how it writes 'agentConfig' vs 'framework'+'rawConfig' - Framework enum table: all 7 values with wire string and server normalizer - framework type corrected: String → Framework enum throughout - Removed stale references: AgentConfigSerializer.AsJson/@JsonSerialize on fields, @JsonInclude(NON_NULL) on class, Plan.AsJson on field — all of these are now inside AgentRequest.Serializer, not annotations - compileAgent code example updated to use Framework.OPENAI enum constant - Structural proof updated to reference gen.writeObjectField('static_plan',...) instead of @JsonProperty annotation Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * docs(java-sdk): add AgentRuntime API reference Covers the full public surface of AgentRuntime — the primary SDK entry point: Constructors (4 overloads) + environment variables table ApiClient factories: clientFromEnv / client(url) / client(url, key, secret) - connectTimeout=10s, readTimeout=30s, writeTimeout=30s baked in run / runAsync — sync + async; Plan overload for PLAN_EXECUTE start / startAsync — fire-and-forget; AgentHandle methods table stream / streamAsync — SSE event iteration; AgentEvent fields table; event-targeted approve/reject for HITL sub-executions plan — CompileResponse; delegates to AgentClient.compileAgent deploy / deployAsync — idempotent registration; Schedule reconciliation overload serve — blocking worker mode; SIGTERM shutdown hook resume / resumeAsync — crash recovery / reconnect to existing execution schedules — lazy Schedules accessor shutdown / close — stops workers + releases OkHttp pool AgentConfig table: workerPollIntervalMs, workerThreadCount + env vars Thread safety: one shared instance per app, shutdown hook pattern Wired into standalone mkdocs.yml and root mkdocs.yml under "API Reference". Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * docs(java-sdk): add Agent field reference + fix missing Javadoc Agent.java — three builder methods that had no Javadoc: handoffs(List/varargs) — SWARM handoff triggers; reference OnTextMention/OnToolResult allowedTransitions(Map) — SWARM transfer restrictions framework(String) — reference Framework enum; explain wire value convention frameworkConfig(Map) — explain spread-at-top-level behaviour sessionId field-level comment: explicitly notes it is NOT in agentConfig (execution parameter, not compilation parameter) — the one subtle field that users most commonly misplace. agent-structure.md — complete field-by-field reference: - 40-row mapping table: Java field → JSON key → server AgentConfig → Python - "NOT in agentConfig" table: sessionId, stateful, framework, frameworkConfig, 4 callback functions — documents WHY each is excluded - "In server but not Java" table: description, memory, reasoningEffort, maskedFields, contextWindowBudget - "In Python but not Java" table: memory, dependencies, reasoning_effort, masked_fields - Serialization rules: strategy emission guard, framework dispatch paths, synthesize default, plannerContext validation, callback serialization - Defaults comparison: Java builder vs server Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * docs(java-sdk): correct gate and enable_planning — both exist in Python gate (TextGate) and enable_planning are present in the Python Agent class (sdk/python/src/agentspan/agents/agent.py lines 360, 369, 527, 589). The 'Not in Python' table was wrong — correct to show Python equivalents: gate → gate (TextGate) enablePlanning → enable_planning Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * docs(java-sdk): validate and correct 'not in Java' field claims Verified by reading: sdk/python/src/agentspan/agents/agent.py (field definitions) sdk/python/src/agentspan/agents/config_serializer.py (serialization) server/conductor-agentspan/src/main/java/.../AgentConfig.java Corrections: memory, reasoning_effort, masked_fields, context_window_budget — exist in Python Agent AND are serialized to the server; genuine Java gaps; moved to 'In Python (and server) but not in Java' table with JSON keys description — exists in server AgentConfig ONLY; neither Java nor Python Agent exposes it (set by UI/platform); moved to its own table gate / enable_planning — already corrected in previous commit Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * docs(java-sdk): correct serialization notes — stateful/sessionId/callbacks ARE emitted The 'Fields NOT in compiled agentConfig' section was wrong about 6 of 8 entries. Verified by reading AgentConfigSerializer.java: sessionId → agentMap.put("sessionId", ...) at line 232 — IS emitted stateful → agentMap.put("stateful", true) at line 466 — IS emitted 4 callbacks → emitted in agentConfig.callbacks list as task entries Only framework (dispatch key) and frameworkConfig (merged/spread) are genuinely not emitted under their own keys in agentConfig. Renamed section to 'Serialization notes for potentially surprising fields' and corrected each row to reflect what actually happens. Also fixed the two stale rows in the main mapping table (stateful, sessionId). Answer to the user question: this does NOT create bugs. All fields are intentionally handled. The previous section title was misleading and most of its content was factually incorrect. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * docs(java-sdk): 3-pass accuracy review — fix all API mismatches vs source Verified every code claim across all 17 docs against actual source. Fixes: Tool registration (4 files): @Tool POJOs use ToolRegistry.fromInstance(obj), NOT AgentTool.from(obj). AgentTool.from takes an Agent (sub-agent → tool). Fixed getting-started, agents, tools, langchain4j + api-reference. Guardrails (agents, guardrails, google-adk): GuardrailDef.regex()/llm()/of() static factories do NOT exist. Rewrote to the real RegexGuardrail.builder() / LLMGuardrail.builder() (return GuardrailDef) and GuardrailDef.builder().func() for custom. OnFail values corrected: RAISE/RETRY/FIX/HUMAN (not BLOCK/WARN). Import corrected: ai.model.GuardrailDef (not ai.guardrail.GuardrailDef). Credentials: Credentials.get(name) takes no ToolContext arg (4 sites). CliConfig: package is ai.execution (not ai.tools); builder has no .command() — only enabled/allowedCommands/timeout/workingDir/allowShell. Scheduling: runNow(ScheduleInfo) not runNow(String); previewNext(cron, n) not nextNExecutions(wireName, n). Fixed scheduling, api-reference, agent-runtime-api. Return types: plan() → CompileResponse (not Map); AgentResult.getOutput() → Object (+ getOutput(Class)); removed non-existent getRawResult(); AgentEvent.getResult() → Object. Removed non-existent no-arg reject(); added isWaiting()/respond(Map). Strategy enum: added ROUND_ROBIN, RANDOM (9 values total). PLAN_EXECUTE: Op.generate takes a Generate object, not boolean — rewrote the multi-agent example to the real args + Ref("stepId") wiring pattern. Defaults: timeoutSeconds default is 0 (server applies its own), not 600. Links: self-hosting link depth corrected (../ not ../../) for the integrated site. Final sweep confirms: 0 residual defect patterns; all doc imports resolve to real source files; all framework bridge signatures match. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * feat(java-sdk): add memory, reasoningEffort, maskedFields, contextWindowBudget to Agent Closes the four field gaps documented in agent-structure.md — Python and the server had these; Java Agent now does too. New on Agent.builder(): .memory(ConversationMemory) → "memory": {messages, maxMessages} (server MemoryConfig) .reasoningEffort(String) → "reasoningEffort" ("low"|"medium"|"high") .maskedFields(String.../List) → "maskedFields" (redacted in history/UI) .contextWindowBudget(int) → "contextWindowBudget" (proactive condensation) New class model/ConversationMemory.java — messages + maxMessages, with addUser/addAssistant/addSystem chaining helpers (mirrors Python ConversationMemory). AgentConfigSerializer emits all four with null/empty guards; JSON keys verified against server AgentConfig field names and Python config_serializer output. Tests (SerializerTest, +2): parity_fields_serialized (proven to fail without the serialization) and parity_fields_absent_when_unset. 206 unit tests, 0 failures. docs: agent-structure.md moves these into the main mapping table; the "Python but not Java" section now lists only `dependencies`. api-reference.md Agent.Builder gains the four methods. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * test(java-sdk/e2e): add round-trip e2e for memory/reasoningEffort/maskedFields/contextWindowBudget Adds 4 structural plan() tests to Suite17NewParity (Order 19-22), matching the suite's existing no-LLM pattern: build agent with the field set → runtime.plan() (real /agent/compile) → assert the field survives the SDK → server → compiled-output round-trip. - test_reasoning_effort_serialized → agentDef.reasoningEffort == "high" - test_context_window_budget_serialized → agentDef.contextWindowBudget == 8000 - test_masked_fields_serialized → maskedFields (agentDef OR WorkflowDef — server maps it to WorkflowDef.maskedFields, so valueFromPlan() checks both) - test_memory_serialized → agentDef.memory.{maxMessages, messages} No LLM (CLAUDE.md). Compiles against real APIs. Requires a live server to run; the exact echo location per field is confirmed on first run (make-fail validation). Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * refactor(java-sdk/e2e): rename Suite17NewParity → Suite17ConfigSerialization "NewParity" described when the tests were added, not what they verify. The suite asserts that agent config fields/features round-trip into the compiled agentDef via plan(). Renamed file + class accordingly and fixed the stale "Suite 11" javadoc. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(java-sdk): trim agent-structure.md to a clean field reference Drop the "Cross-Layer Proof" framing, the defensive "all fields serialize correctly / none are missing" reassurances, and the cross-SDK parity-proof sections. Keep the factual reference: fields, builder methods, JSON keys, serialization behavior, and defaults. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(java-sdk): add agent-schema.json wire contract + verified proof Canonical JSON Schema (Draft 2020-12) for the agentConfig that SDKs serialize and POST to the server. Reconciled from the server AgentConfig model (the deserialization target) and both SDK serializers, verified in 3 rounds: 1. Static inventory of server model + nested configs + both SDK emit sets. 2. Direct-source verification of cross-SDK discrepancies — corrected the onFail enum (retry|raise|fix|human), strategy nullability, sessionId/ planSource channel divergences, reasoningEffort value set. 3. Empirical: maximal agent serialized by BOTH SDKs validates against the schema; 6 negative mutations all rejected (schema has teeth); the Java-emitted, schema-valid config compiles on a live server (HTTP 200). agent-schema.md documents the schema and carries the formal proof (soundness/completeness/type-consistency) and the known cross-SDK divergences. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(java-sdk/e2e): fix ScheduleIntegrationTest silently skipping under standard env The suite appends its own "/api" to AGENTSPAN_SERVER_URL, but that env var conventionally INCLUDES "/api" (per BaseTest and CI). The mismatch produced a double "/api", so the @EnabledIf scheduler probe 404'd and all 10 tests skipped under the normal convention — i.e. they never ran. Normalize the base URL by stripping a trailing "/api" so the suite runs under either form. Verified: with the standard /api env the suite went from 10 skipped → 10 run, 0 failures; full e2e is now 346 tests, 0 skipped, 0 failures. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(java-sdk): reverse-verify agent-schema via generated dataclass + record generate.py reverse-engineers agent-schema.json into a Python dataclass and a Java record, then proves correctness by diffing the generated models against the server AgentConfig models and validating a generated instance: (a) generated fields ≡ schema (root + 16 nested $defs) (b) generated instance validates against the schema (c) every server field — root + all 13 nested models — is in the schema (0 gaps) The reverse pass confirms nothing was missed. Only surfaced one undocumented SDK-only extra, cliConfig.workingDir (Java emits it; server CliConfig ignores it), now noted in the divergences alongside the tool retry fields. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(server): OCG sub-agent — system tasks + agent-driven auto-injection Adds an optional OCG (Open Context Graph) retrieval sub-agent that the main agent's LLM can delegate to when it decides it needs context. Whole feature is gated on agentspan.ocg.url; unset means every OCG bean stays out of the context and no behavior changes. - 7 OCG_* WorkflowSystemTask beans (OcgRequestTask) — one per OCG endpoint (query, get_entity, neighborhood, code_history, memory_set/reinforce/delete). Each proxies a single HTTP call with field projection + response capping (response-cap-chars, default 8192) so a large graph traversal can't blow the model's context. - _ocg_agent workflow registered at startup by OcgSubAgentService — a normal AgentConfig built by OcgAgentFactory with the OCG system prompt and the seven ocg_* tools. - OcgAgentToolInjector silently appends an ocg_agent agent_tool to every top-level AgentConfig at compile time (skips self-injection on _ocg_agent and duplicate injection if a user already declared it). Main agent's LLM sees it as a peer it can call; tool call dispatches SUB_WORKFLOW(_ocg_agent) which runs its own LLM ↔ ocg_* tool loop and returns a synthesized answer. - ToolCompiler TYPE_MAP + enrichment script (both static and dynamic variants) get an OCG bucket so ocg_* tools route to the right OCG_* task type at runtime. * feat(ocg): send bearer auth header on OCG requests Wires OCG_API_KEY env → agentspan.ocg.api-key → Authorization: Bearer header on every OCG_* system task's HTTP request. Empty key keeps the header off so unauthenticated local OCG instances still work. * refactor(server): generic auto-expose mechanism + fix OCG dispatch Two intertwined changes that ended up in one commit because they touch the same files: 1) Fix OCG end-to-end dispatch (was broken on the previous tip). - Register a TaskDef for each ocg_* tool name in OcgSubAgentService. Conductor resolves dynamic-fork tasks by name in the TaskDef registry; without the def, dispatch failed with "Cannot find task by name ocg_query in the task definitions". - Prefix every OCG endpoint with /api/v1 (was hitting /agent/query etc., the real paths are /api/v1/agent/query etc.). The OCG service returned grpc-gateway 404 NOT_FOUND on every call. With both fixes the full chain works: main LLM → ocg_agent SUB_WORKFLOW → ocg_query OCG_QUERY task → POST /api/v1/agent/query → real citations back. 2) Replace the OCG-specific Injector with a generic compiler hook. - AgentCompiler picks up any WorkflowDef whose metadata carries the agentspan.autoExposeAsTool flag and appends it as an agent_tool on every top-level compile. Self-recursion + duplicate guards live inside the merger. Optional @Autowired MetadataDAO keeps existing `new AgentCompiler()` test paths working unchanged. - OcgSubAgentService stamps the flag on _ocg_agent's metadata at startup. That's the *only* line that ties OCG to LLM visibility — everything else is generic. - OcgAgentToolInjector + its 5-test pinning class are deleted. AutoExposedToolsMergeTest (6 tests) replaces them and is deliberately not OCG-specific, so future server-side sub-agents rely on the same contract. - AgentService loses its @Autowired OcgSubAgentService field and the maybeInjectOcgAgentTool helper. resolveConfig is back to a plain normalize-or-passthrough. Future server-side sub-agents (`_foo_agent`, `_bar_agent`, …) now drop in by stamping the same metadata flag and registering their workflow. No AgentCompiler change, no AgentService change, no per-feature injection class. * refactor(java-sdk): remove Agentspan static facade; AgentRuntime is the entry point The Agentspan static facade wrapped a process-global singleton AgentRuntime with a JVM shutdown hook — a crutch that hurt testability/DI and was the odd one out (AgentRuntime is already AutoCloseable, and the Spring module auto-configures an agentRuntime bean). Users now either construct an AgentRuntime (try-with-resources) or inject the Spring bean. - Move the native-framework drop-in overloads (run/start/stream/deploy/serve/plan/ resume accepting a raw ADK BaseAgent, LangChain4j ChatModel, or LangGraph4j AgentExecutor.Builder) + the coerceAgent/reflection helpers ONTO AgentRuntime as instance methods, so the runtime is now the complete API. No capability lost. - Delete Agentspan.java. - Rewrite 147 examples to construct a main-local AgentRuntime and call runtime.*. - Update README, framework-bridge Javadoc, and docs to AgentRuntime. Builds: core + examples + spring compile; unit tests 206/0/0. AgentRuntime change is purely additive, and e2e suites already use AgentRuntime directly (unaffected). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(java-sdk): remove UserProxyAgent — non-functional parity stub UserProxyAgent.create(...) only stamped inert metadata (_agent_type=user_proxy, _human_input_mode, _default_response) that NOTHING consumes — the server has no user-proxy/human-input-mode handling, so the documented "pauses with a HumanTask and waits for real human input" behaviour never happened. It produced a plain LLM agent with misleading metadata, was used only by its own tests (no examples, no production path), and its class Javadoc example referenced a 1-arg create(String) overload that didn't even exist. Real HITL is HumanTool / WaitForMessageTool / MANUAL strategy. Deletes UserProxyAgent.java + its 2 unit tests (SerializerTest) + 1 e2e test (Suite17ConfigSerialization) and the stale references. Builds green; full unit+e2e 343/0/0 against a live server. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor: remove UserProxyAgent from Python, TypeScript, and C# SDKs Cross-SDK parity with the Java removal: UserProxyAgent only stamped inert _agent_type=user_proxy / _human_input_mode / _default_response metadata that the server never interprets, so the documented "pauses for human input" behaviour never happened. Real HITL is human_tool / wait-for-message / MANUAL strategy. Removed in each SDK: the class, its export, its dedicated example (27_*), its kitchen-sink usage, and its tests — keeping GPTAssistantAgent intact. Also updated current API docs (docs/python-sdk/*), the SDK READMEs/CHANGELOG, the python validation group, top-level README, and AGENTS.md. Left untouched: historical design records under docs/sdk-design/ and docs/design/ (dated artifacts; flagged separately). Verified: Python 1653 unit tests pass; TypeScript builds + 822 unit tests pass; C# builds + 161 tests pass. Repo grep clean outside the design docs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: scrub UserProxyAgent from design records (sdk-design + design plans/specs) Completes the cross-SDK removal: the design docs no longer reference UserProxyAgent now that it's gone from all SDKs. Removed its sections, feature-matrix/example rows, source-tree listing mentions, prose list items, and the editorial_reviewer kitchen-sink participant across the multi-language design, TS/Java/Go/Ruby/Kotlin docs, and the 2026-03 plans/specs. GPTAssistantAgent and all other content kept intact; historical feature/section numbering left as-is. Repo-wide grep for UserProxyAgent/user_proxy is now zero (outside build artifacts). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor * refactor(java-sdk): unify credential access onto ToolContext; drop static Credentials Tool secrets were read via a public static thread-local accessor (Credentials.get), inconsistent with every other per-call value (ids, shared state) which arrives on the injected ToolContext — and it leaked framework-only setForCall/clearForCall onto a public class. Now a tool reads ctx.getCredential(name) / getCredentialOrNull(name) on its ToolContext, one injected object, no public framework hooks. - ToolContext gains an IMMUTABLE per-call credential snapshot + getCredential/ getCredentialOrNull/getCredentials. Multi-threading: because the snapshot lives on the context object (not a thread-local), a thread the tool spawns can read it, and it stays valid after the worker thread clears the transport — the old static silently failed off-thread. - New internal CredentialContext (thread-local) carries resolved secrets from WorkerManager → ToolRegistry on the worker thread (secrets never enter task input/output). - Deleted the public Credentials class; updated Example16, e2e Suite2, WorkerCredentialFetcher javadoc; replaced CredentialsTest with ToolContextCredentialsTest. Verified: full unit+e2e 344/0/0 (incl. Suite2 credential round-trip on a live server + secret store). Make-fail: dropping the snapshot fails 4 unit tests incl. the multi-thread guarantee, confirming the suite has teeth. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(java-sdk): remove ClaudeCode — non-functional stub in Java The Java ClaudeCode class advertised a PermissionMode it silently dropped: toModelString() encodes only "claude-code/{model}", the serializer never emits permissionMode, and the server never reads it — so new ClaudeCode("opus", BYPASS) had zero effect. Worse, Java has no client-side claude-agent-sdk worker (Python runs claude-code agents via a local claude_agent_sdk worker and emits a passthrough stub; Java emits a plain native agent and has no worker to execute it), so the class couldn't deliver a working feature at all. Used only by tests; no examples. Deletes ClaudeCode.java + its 3 unit tests (SerializerTest) + 2 e2e tests (Suite17ConfigSerialization) and the import/javadoc references. Targeting a claude-code-capable server is still possible via a raw model string if ever wired. Python keeps its ClaudeCode — there permission_mode IS consumed by the claude_agent_sdk runtime worker, so it's functional and stays. Compiles (core + e2e + examples); unit tests pass, 0 failures. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(java-sdk): drop ClaudeCode test references (completes prior commit) The previous commit deleted ClaudeCode.java but a failed `git add` left these two test files out, so that revision didn't compile. This removes the ClaudeCode unit tests (SerializerTest) and e2e tests + import/javadoc (Suite17ConfigSerialization) that referenced the deleted class. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(java-sdk): keep optional framework types out of AgentRuntime signatures; fix spring prop prefix CI java-sdk-tests failed: :spring:test threw NoClassDefFoundError for org.bsc.langgraph4j...AgentExecutor$Builder. Root cause: moving the TYPED LangChain4j/LangGraph4j drop-in overloads onto AgentRuntime put compileOnly framework types into the core class's method signatures. Spring introspects the AgentRuntime bean's methods (resolving every parameter type), which force-loads those optional types — absent from the spring classpath. The old Agentspan facade had the same overloads but Spring never loaded it, so the issue was latent. Fix: AgentRuntime now exposes only Object-typed drop-ins. The native LangChain4j ChatModel / LangGraph4j AgentExecutor.Builder are detected reflectively in coerceAgent (by FQN, same pattern as ADK BaseAgent) and built in method BODIES, so no compileOnly type appears in any signature — Spring introspection is safe. Added run/runAsync/start/stream(Object, String, Object... tools) for the tool-POJO form; fixed-arity overloads still win resolution (internal null calls cast to Plan). Examples calling runtime.run(model|builder, prompt[, tools]) are unchanged. Also fixes a separate spring-rename mismatch this unmasked: AgentProperties was @ConfigurationProperties(prefix="conductor.agent") while its tests and own Javadoc use the agentspan.* prefix — restored prefix to "agentspan". Verified: :test + :spring:test (5/0/0) green; examples + e2e compile. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(ocg): strategy pattern + Apache Commons cleanup Replace the switch-statement-heavy OcgRequestTask with a per-endpoint strategy. Each operation owns its URL/method/body/projection in its own class; the task itself is a thin orchestrator. - OcgOperation interface: taskType(), name(), build(), project() - Seven concrete operations under runtime/ocg/operation/ — one per endpoint (query, get_entity, neighborhood, code_history, memory_{set,reinforce,delete}) - Three shared utilities in the same sub-package: OcgInputs — pick/required/intOrDefault/parseJsonLenient/writeJson OcgUri — UriComponentsBuilder rooted at /api/v1 OcgRequest — HttpRequest factory: base() / postJson() / get() / delete() - Apache Commons replaces hand-rolled helpers: StringUtils.removeEnd → trim trailing slash StringUtils.abbreviate → response cap (with custom marker) and log-body truncation Validate.isTrue → required-input check NumberUtils.toInt → string-to-int fallback - Spring UriComponentsBuilder handles URL encoding properly. One behavioural improvement: query params no longer over-encode ':' to '%3A' (it's not reserved in RFC 3986 query values). Updated the memoryDelete test assertion accordingly. - OcgRequestTask is now ~100 lines total. Largest method is 16 lines; every other method is under 10. No switch statements anywhere. - OcgRequestTaskConfig becomes one @Bean per operation, each pairing a fresh OcgRequestTask with its strategy. End-to-end smoke test on the live OCG dev instance still works — same shape, same citations, same token count. * refactor(server): generic registry for server-registered agents Replace OCG's bespoke @PostConstruct registration with a generic two-bean registry that any future server-side sub-agent can plug into without writing per-feature service code. Generic infrastructure (runtime/registry/, OCG-agnostic): - RegisteredAgent — interface; agentConfig() + autoExpose() - RegisteredAgentRegistrar — @PostConstruct picks up every bean, compiles via AgentCompiler, stamps the auto-expose marker when requested, and writes to MetadataDAO - RegisteredTaskDefs — interface; taskDefs() - RegisteredTaskDefsRegistrar — runs first via @DependsOn OCG plug-in (just two @Components — no @Configuration wrapper): - OcgRegisteredAgent — implements RegisteredAgent - OcgRegisteredTaskDefs — implements RegisteredTaskDefs - OcgSubAgentService — DELETED (responsibilities moved to the two registrars; no per-feature @PostConstruct anywhere now) Both @Components carry @ConditionalOnExpression rather than @ConditionalOnProperty: the latter treats empty strings as "present and not false", so empty OCG_URL would still instantiate the beans and leak the auto-expose registration into the test DB. The same conditional is now on OcgRequestTaskConfig for consistency. Two new test classes (6 tests total) pin the contract every future sub-agent relies on: compile, stamp-if-exposed, persist; no stamp when autoExpose() returns null; empty supplier lists are no-ops. End-to-end OCG smoke test on the live dev instance still works identically — same shape, same citations, same token count. * refactor(compiler): split mergeAutoExposedTools into focused helpers The method was a 60-line block mashing six concerns: DAO fetch with try/catch, existing-name collection, per-workflow metadata parsing, self/duplicate guards, ToolConfig construction, and write-back. Split into: - mergeAutoExposedTools — the loop, ~15 lines, zero control-flow keywords inside the body (no continue/break). Reads as: "fetch defs, collect taken names, for each def Optional.ifPresent build, commit if anything was added." - tryBuildAgentTool — per-workflow filter chain with named guard clauses; returns Optional<ToolConfig> - safelyFetchAllWorkflowDefs — the DAO call + warn-on-failure - collectToolNames — existing-name accumulation - readAutoExposeSpec — metadata parsing into a typed AutoExposeSpec record - buildAgentTool — ToolConfig construction - appendTools — copy-on-write list mutation The two-pass "contains then add" dedup collapses into a single takenNames.add(...) check that does both, halving the guard lines. Behavior unchanged — 714 tests still green. * feat(ocg): bake current date into OCG sub-agent system prompt The OCG sub-agent's LLM was hallucinating date ranges (e.g. picking 2023-10-10 as end_time when "today" is 2026-06-09), filtering out fresh data and producing thin synthesis. Anchor the LLM on a real date: - Add TODAY_PLACEHOLDER ({{TODAY}}) to OCG_SYSTEM_PROMPT - OcgAgentFactory.build() replaces it with LocalDate.now(UTC) at workflow-compile time - Prompt opens with "Today's date is <today> (UTC)" and explicit guidance to anchor relative ranges on it and omit ranges when none are implied Refreshes on every server restart since RegisteredAgentRegistrar recompiles _ocg_agent at startup. Long-running servers will drift — fix can move to a runtime-resolved template var later if that matters. * fix(ocg): import java.time classes instead of inline FQN The CI guard 'checkNoInlineFQN' caught the inline java.time.LocalDate.now(java.time.ZoneOffset.UTC) call I added when baking today's date into the OCG system prompt. * Add docs * refactor(compiler): top-level-only auto-expose, constructor injection, lazy cache Three concerns from the OCG branch code review: 1. mergeAutoExposedTools used to fire on every recursive compile because compileSubAgent (and the graph-structure subgraph compile, and MultiAgentCompiler's swarm inner-workflow compile) all called back into the public compile() entry. The Javadoc claimed top-level-only — the code contradicted it. Nested specialist sub-agents silently inherited ocg_agent and the DAO got re-queried per nesting level. Public compile() now runs the merge then delegates to a new package-private compileWithoutAutoExpose() that does the strategy dispatch + post-processing. The three internal recursion sites switch to the non-merging entry. Pinned by mergeRunsOnceAtTopLevelOnlyAndSkipsInternalRecursion. 2. MetadataDAO was field-injected with @Autowired(required = false), forcing AutoExposedToolsMergeTest to use ReflectionTestUtils. Switched to constructor injection with a no-arg overload that preserves existing `new AgentCompiler()` call sites; AutoExposedToolsMergeTest setUp now uses `new AgentCompiler(metadataDAO)`. 3. safelyFetchAllWorkflowDefs hit the DAO on every compile. Registered server-side agents are written at @PostConstruct and don't change at runtime, so the per-request fetch was wasted work. Added a volatile List<AutoExposedEntry> cache with double-checked locking: successes cached for the lifetime of the bean, transient DAO failures left uncached so the next compile retries (matches the existing "merge is a convenience, not a correctness requirement" contract). Three new tests in AutoExposedToolsMergeTest pin the contracts. Each was verified failing against the broken state before landing the fix. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(ocg): preserve interrupt flag, tighten throws, pin body shapes Smaller review items from the OCG branch: - OcgRequestTask.start used to catch Exception, which swallowed InterruptedException without re-flagging the current thread. A task cancelled mid-http.send would appear to "fail" silently and Conductor's executor would never observe the cancellation. Now catches InterruptedException separately and calls Thread.currentThread().interrupt() before failing. The remaining catch is IOException | RuntimeException so Error still propagates. - OcgRequestTask.send/complete declared `throws Exception`; tightened to the actual checked exceptions. OcgOperation.build is also tightened from `throws Exception` to `throws IOException` (JsonProcessingException extends IOException, so the postJson-using operations still compile). - Two missing OcgRequestTaskTest cases for the memory_set and memory_reinforce body shapes: memory_set must strip the server-side __agentspan_ctx__ glob before forwarding; memory_reinforce must only forward the four picked fields (no key, no __agentspan_ctx__, no rogue fields the LLM might attach). Tests read the actual HttpRequest body via a Flow.Subscriber helper. - OcgAgentFactoryTest now pins the {{TODAY}} substitution and the absence of the literal placeholder in the rendered prompt. Each new test was verified failing against an intentionally broken state before landing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(compiler): stop registrar from snapshotting empty auto-expose cache at boot `RegisteredAgentRegistrar.register()` was calling `agentCompiler.compile()`, which triggers the auto-expose merger's lazy DAO scan. During the registrar's own `@PostConstruct` loop the to-be-registered agent isn't yet in the DAO, so the merger cached an empty list and froze it for the bean's lifetime — every user compile post-startup saw no auto-exposed tools and the OCG sub-agent was silently invisible to every LLM. End-to-end smoke against a live server confirmed the bug, then confirmed the fix. - Promote `AgentCompiler.compileWithoutAutoExpose` to public; registrar now calls it so bootstrap never touches the merger cache. - Extract `AutoExposedToolsMerger` (was ~140 lines inside `AgentCompiler`) as its own `@Component`. AgentCompiler keeps a thin `mergeAutoExposedTools` delegate for the existing test API. - Add `RegisteredAgentBootstrapTest` — stateful in-memory `MetadataDAO` exercises register → user-merge ordering. Verified failing on pre-fix code, passing post-fix. - Update `RegisteredAgentRegistrarTest` mocks to track the new entry point. - Drop duplicate `agentspan.ocg.response-cap-chars` default from `application.properties` (already in `OcgProperties`). - Simplify `OcgMemoryDeleteOperation.addQueryParamIfPresent`: drop the redundant `StringUtils.defaultString` wrapping. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * style: apply spotless / palantir-java-format violations from CI CI flagged a handful of line-wrap differences in files touched by the previous fix. Local palantir-java-format 2.50.0 + Zulu JDK 21 throws NoSuchMethodError so spotlessApply is unavailable here; applying the hunks reported in the CI build log by hand. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * style: collapse remaining spotless violation in OcgRequestTaskTest Last hunk CI flagged after the prior fixup landed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(ocg): require OCG_MODEL explicitly when OCG is enabled Drop the silent ``openai/gpt-4o-mini`` default for ``agentspan.ocg.model``. The right model depends on cost, latency, and the OCG corpus, so it has to be an explicit operator decision rather than an inherited fallback. ``OcgAgentFactory.build`` now throws ``IllegalArgumentException`` with an operator-actionable message when ``OCG_URL`` is set but ``OCG_MODEL`` is blank, so boot fails fast instead of silently routing OCG traffic through the wrong model. ``OCG_MODEL`` is documented as required (alongside ``OCG_URL``) in the docs setup table; the optional-knobs table no longer lists it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * agentdef * Support Agentspan Embedded in Orkes Conductor (#273) * Spotless * Spotless * refactor(compiler): source auto-exposed tools from RegisteredAgent beans, not a DAO scan AutoExposedToolsMerger previously discovered auto-exposed sub-agents by scanning the metadata store for WorkflowDefs stamped with a metadata marker, caching the result forever. The data it was reading back came from the same Spring context: the registrar wrote RegisteredAgent beans into the DAO, then the merger scanned them back out. That round-trip is what created the bootstrap-ordering trap (a compile during @PostConstruct froze an empty cache for the bean's lifetime, silently hiding every registered agent — see 879851f8) plus the lazy cache, the transient-failure retry carve-out, and the untyped metadata parsing. Now the merger builds its entries from List<RegisteredAgent> at construction, so it is complete before any compile can run and the trap is structurally impossible. Deleted along the way: - the AUTO_EXPOSE_AS_TOOL_METADATA_KEY wire protocol (stamping in the registrar, parsing in the merger, the re-export on AgentCompiler) - the volatile cache + double-checked locking + failure-retry logic - AgentCompiler's MetadataDAO constructor and mergeAutoExposedTools delegate The registrar still persists each WorkflowDef — that's needed for SUB_WORKFLOW dispatch by name — but it's now a dumb compile-and-persist loop. A blank autoExpose tool name now fails fast at boot instead of being silently skipped. Tests rewritten to the bean-list model; the obsolete cache-behavior tests are replaced by construction-time pins. Validated per CLAUDE.md by mutating merge() into a no-op: exactly the injection-asserting tests failed, guards stayed green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ocg): anchor the sub-agent's "today" at execution time, not boot time OcgAgentFactory baked LocalDate.now() into the registered system prompt, so the date anchor for "recent" / relative-range queries was the server's boot date. On a long-running server the prompt drifts until it claims last week (or last month) is "today", and the LLM bounds every relative query against the wrong anchor — the exact hallucinated-date failure the anchor was added to prevent. Now the agent_tool dispatch script (which runs per execution) injects __today__ = current UTC date into every sub-workflow's input, and the OCG prompt references ${workflow.input.__today__}, substituted by Conductor when the LLM task is scheduled. The injection is generic: any future sub-agent prompt can use the same input. Tests written first and confirmed red before the fix, per CLAUDE.md: - OcgAgentFactoryTest pins the prompt to the runtime expression - EnrichToolsScriptTest executes the real dispatch script in GraalJS and asserts the SUB_WORKFLOW input carries a yyyy-MM-dd __today__ Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ocg): tell the model to omit …
The typescript-unit-tests CI job's `npm audit --omit=dev --audit-level=high` gate was failing (also red on main) on two high-severity transitive CVEs that ship to users: - undici <=7.27.2 (TLS validation bypass, header injection, cache poisoning, DoS) — pulled via @io-orkes/conductor-javascript - ws 8.0.0-8.20.1 (memory-exhaustion DoS) — deduped transitive Pin both to patched versions via npm `overrides` (undici ^7.28.0, ws ^8.21.0) — same major, drop-in. Lockfiles regenerated and kept consistent. Verified: `npm ci` + `npm run build` + `npx vitest run tests/unit/` (823 tests) all pass, and the audit gate now exits 0. Remaining moderate/low advisories (uuid, esbuild) are below the high threshold and do not fail the gate.
… fix/hitl-pending-tool-calls
The python-e2e job drives a real server + real LLM, so individual tests flake nondeterministically on transient conditions — workflow still RUNNING at the client timeout, tool-call batches not returning, LLM phrasing variance. A single transient failure currently fails the whole job; observed runs failed a *different* unrelated test each time (test_after_tool_callback_executes, test_http_lifecycle, ...). Add pytest-rerunfailures and run e2e with --reruns 2 --reruns-delay 5. A genuinely broken test still fails all 3 attempts; a one-off flake recovers. Does not mask real regressions, just transient infra/LLM noise. Dep added to the dev extra; uv.lock updated (pytest-rerunfailures 16.3).
…es) (#278) The python-e2e suite drives a real server + real LLM, so individual tests flake nondeterministically on transient conditions — workflow still RUNNING at the client timeout, tool-call batches not returning, LLM phrasing variance. A single transient failure currently fails the whole job; observed runs failed a *different* unrelated test each time (test_after_tool_callback_executes, test_http_lifecycle@credentials, ...). Add pytest-rerunfailures and mark every e2e item flaky(reruns=2, reruns_delay=5) via the e2e conftest. A genuinely broken test still fails all 3 attempts; a one-off flake recovers. Configured in conftest (not the CI yaml) so it also covers local e2e runs and needs no workflow-file change. dev extra + uv.lock updated (pytest-rerunfailures 16.3).
# Conflicts: # sdk/python/src/agentspan/agents/ocg.py
…282) release-server-maven.yml only had a workflow_dispatch trigger, so creating a release (e.g. v0.2.0) never published the server modules. Add a release: [created] trigger with the same version-extraction the Python/JS SDK workflows use, so it fires automatically on release and stays manually dispatchable. Also publish both modules (conductor-agentspan, conductor-agentspan-server) to GitHub Packages: add a GitHubPackages maven repository to the server Gradle build (publishAllPublicationsToGitHubPackagesRepository) and a separate least-privilege publish-github-packages job (packages: write + GITHUB_TOKEN, no Sonatype/signing secrets).
The guardrail matrix's #1 aout_regex_retry hit TIMEOUT in CI while its raise/fix siblings passed. It's the only CC-regex spec that drives the agent-level retry loop, and INST_CC unconditionally ordered the model to emit the card number verbatim with no redact-on-retry clause — so every retry re-emitted the CC, the regex re-blocked, and it burned all maxTurns. Combined with the new LLM_CHAT_COMPLETE task-level retry (retryCount=3, exponential backoff) under 27-way concurrency, that tipped it past the 300s polling budget. - Make INST_CC retry-friendly (echo verbatim first, redact on retry) so #1 converges in ~2 turns — same pattern already used for INST_PROC/INST_SECRET. - Widen the poll budget 300s -> 480s for headroom on the LLM retry backoff, still under the 600s beforeAll/describe timeouts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rewrite design-doc §9.2 around the three concrete ways a consumer picks an AgentSpan + Conductor pair, replacing the open-ended "every consumer of the published library" framing: - Mode A (standalone server): drift-free by construction (single conductorVersion, lib+server same commit). - Mode B (external OSS self-embed): build from source against your engine (recommended) inherits Mode A's by-construction guarantee; take-the-published-jar + self-certify is the best-effort fallback, with a Conductor-Built-Against manifest breadcrumb for diagnosability. - Mode C (orkes enterprise embed): one host-pinned pair, host-certified. Add a forward-reference from §6 (build/classpath mechanics) to the §9.2 mode table (ownership per deployment) so the sections stay in sync. The README "Releasing" section documents the matching release-time convention (single conductorVersion, build-against fact not a range). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- §9.3 (upgrade & adoption): version upgrade = data + ops (two schema lifecycles, in-flight/HITL deserialization, restore-from-backup); adoption = additive with >= same-major engine-direction rule (Mode A check-or-fall-back-to-B, B aligned-by-construction, C auto-enforced by orkes' release line); SPI fail-fast; removal asymmetry. - New agentspan-validation-readiness.md: gap analysis for the testing/deployment/integration-validation task. Notes the SDK e2e suite is already black-box/endpoint-parameterizable (instrument exists), documents the three orkes-reuse options (A works today, C conformance container does not exist yet), and flags security validation + post-deploy smoke as the real uncovered gaps. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…, CLI, python -m) (#279) * fix(python-sdk): support `python -m agentspan` via __main__.py Windows users (esp. when the install's Scripts dir isn't on PATH) fall back to `python -m agentspan ...` and hit: No module named agentspan.__main__; 'agentspan' is a package and cannot be directly executed because there was no __main__.py — the CLI was only reachable through the `agentspan` console script (entry point agentspan.cli:main). Add agentspan/__main__.py that calls cli.main(), so `python -m agentspan doctor` is equivalent to `agentspan doctor` and works without the Scripts/bin dir on PATH. Verified locally: `python -m agentspan doctor` and `--help` now run (exit 0). Adds a deterministic test that __main__ re-exports cli.main and that importing it is side-effect free (validated fail-then-pass). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(python-sdk): harden Windows/cross-platform developer experience Follow-up to the __main__.py fix, from a full audit of the fresh-developer path (install -> invoke -> run). The Windows CLI binary IS published; the defects were in our Python layer. - pyproject: cap requires-python to >=3.10,<3.14 and drop the stale 3.9 classifier. Native deps (grpc via conductor-python, pydantic-core) lack wheels for brand-new Python, so `pip install` on 3.14 fell back to source builds and failed — the exact error the Windows dev on 3.14 hit. - cli binary download (cli/__init__.py): was the fragile core of `agentspan`. Now: atomic download (temp file + os.replace) so a partial/interrupted download can't leave a corrupt binary cached and executed forever; friendly, actionable errors for 404 (unsupported/unpublished platform) and network/proxy failures instead of raw tracebacks; skip POSIX chmod on Windows; treat zero-byte cache as missing; AGENTSPAN_FORCE_DOWNLOAD=1 to recover from a bad cache; main() prints a clean error instead of crashing. - README: Windows venv activation + Python version guidance + the `python -m agentspan` fallback. Tests (deterministic, no network; validated fail-then-pass): atomic success leaves no temp, partial download is cleaned up (no corrupt binary), and 404 / network failures map to friendly RuntimeErrors. Deferred (separate PR): 13 asyncio.get_event_loop() call sites that are deprecated and break on 3.14 — needs a careful, tested sweep. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Feature/improve ocg agent
…ion-alignment docs(server): scope version-drift to three consumption modes
…tor Agent SDK docs, example modernization (#285)
Updates package.json name, all import references in src/, tests/, examples/, docs/, and root-level design/README files.
…ty-215 # Conflicts: # sdk/python/src/agentspan/agents/runtime/http_client.py # server/conductor-agentspan-server/src/main/resources/schema-eval-postgres.sql # server/conductor-agentspan-server/src/main/resources/schema-eval.sql # ui/src/components/Sidebar/sidebarCoreItems.tsx # ui/src/routes/routes.tsx # ui/src/utils/constants/route.ts
…reated-by - Thread a `dataset` field end-to-end (SDK run(dataset=...) → server eval_runs column + migration → EvalRunDto → UI) so an eval run links back to the stored dataset it came from; eval-run detail shows a clickable "Dataset: X" - Auto-run semantic_criteria as an LLM-judge check: semantic.judge_output() returns (score, reason); _run_case records an EvalCheckResult with score + reasoning, skips gracefully when litellm is absent, and surfaces judge errors as a failed check - Store the anonymous OSS user id as null so the UI shows the friendly "Ran by: <script>" instead of an all-zeros UUID - Add pizza_support_eval.py: one script that pushes a dataset and runs the eval linked to it - Tests: dataset round-trip (EvalServiceTest), semantic wiring (4 new unit tests, LLM judge patched — no real calls)
…bed-spike # Conflicts: # sdk/python/src/agentspan/agents/runtime/http_client.py # sdk/python/src/conductor/ai/agents/_internal/token_utils.py # sdk/python/src/conductor/ai/agents/frameworks/claude_agent_sdk.py # sdk/python/src/conductor/ai/agents/frameworks/langchain.py # sdk/python/src/conductor/ai/agents/frameworks/langgraph.py # sdk/python/src/conductor/ai/agents/runtime/runtime.py # sdk/python/tests/unit/test_sse_client.py # sdk/python/tests/unit/test_token_utils.py # server/conductor-agentspan-server/src/main/java/dev/agentspan/runtime/service/skill/FileSystemSkillMetadataDAO.java # server/conductor-agentspan/src/main/java/dev/agentspan/runtime/service/AgentService.java
vishesh-orkes
temporarily deployed
to
maven-central
July 1, 2026 15:31 — with
GitHub Actions
Inactive
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.
Pull for the eval feature