Personal project. No production SLA, no external support, no compatibility guarantees. The API surface changes on the author's schedule. Use at your own risk.
개인 프로젝트입니다. 프로덕션 SLA, 외부 지원, 호환성 보증 없음. 사용 시 자기 책임.
OCaml agent SDK on OCaml 5.x + Eio. Single-process, single-Eio-domain runtime. Talks to Anthropic Messages API and OpenAI-compatible chat endpoints (which covers many local servers, OpenRouter, and similar gateways), plus a local llama-server profile. See lib/api_*.ml for the actually wired endpoints.
- OCaml package:
agent_sdk(inlib/) - OCaml module:
Agent_sdk - Requires: OCaml >= 5.1, Dune >= 3.11
- Current version: source of truth is
lib/sdk_version.ml
opam pin add agent_sdk git+https://github.com/jeong-sik/oas.git#main --yesFor development (with test dependencies):
git clone https://github.com/jeong-sik/oas.git && cd oas
# Required fork pins (OCaml 5.4 compat)
source scripts/mcp-sdk-pin.sh
opam pin add bisect_ppx git+https://github.com/patricoferris/bisect_ppx.git#5.2 --no-action --yes
opam pin add mcp_protocol "git+${MCP_SDK_URL}#${MCP_SDK_SHA}" --no-action --yes
opam install . --deps-only --with-test --yes
scripts/dune-local.sh build @allopen Agent_sdk
let () =
Eio_main.run @@ fun env ->
let net = Eio.Stdenv.net env in
Eio.Switch.run @@ fun sw ->
let agent =
Agent.create ~net
~config:
{ (Types.default_config ~model:"qwen3.5") with
name = "hello";
system_prompt = Some "You are a helpful assistant. Be concise.";
}
~tools:[] ()
in
match Agent.run ~sw agent "What is 2 + 2?" with
| Ok response ->
List.iter
(function Types.Text t -> print_endline t | _ -> ())
response.content
| Error e -> prerr_endline (Error.to_string e)This assumes a local LLM server (llama-server) on port 8085. For Anthropic API, configure a provider:
let agent =
Agent.create ~net
~config:{ (Types.default_config ~model:"claude-sonnet-4-6") with name = "hello" }
~options:{ Agent.default_options with
provider = Some (Provider.anthropic ~model_id:"claude-sonnet-4-6" ());
}
~tools:[] ()See examples/ for more: basic_agent.ml, tool_use.ml,
async_agent_demo.ml, streaming.ml, review_agent.ml,
observable_agent.ml, and builder_patterns.ml.
For tool concurrency rules see docs/tool-concurrency.md.
The provider variants actually wired in lib/provider.ml are:
| Variant | Constructor | Endpoint |
|---|---|---|
Local (llama-server) |
Provider.Local { base_url } / Provider.local_llm ~base_url ~model_id () |
Caller-selected endpoint |
Anthropic |
Provider.Anthropic / Provider.anthropic ~model_id () |
https://api.anthropic.com |
OpenAICompat |
Provider.OpenAICompat { base_url; ... } / Provider.openrouter ~model_id () |
Any /chat/completions host |
There is no separate Gemini variant in the wired provider type — Gemini is reached through an OpenAI-compatible endpoint when one is available. If you need first-class Gemini support, that is currently a planned item, not a shipped one.
Provider.resolve returns (base_url * api_key * headers, sdk_error) result. Missing env vars produce Error, not silent fallback.
OAS ships one opam library in this repository:
Layer 1: agent_sdk (lib/)
| Single-agent runtime: turn loop, tool dispatch, hooks.
|
+-- lib/agent/ Agent lifecycle, turns, tools, handoff, builder
+-- lib/pipeline/ 6-stage turn pipeline
+-- lib/protocol/ A2A, MCP, Agent Card, Agent Registry
+-- lib/llm_provider/ Shared LLM types, HTTP client, streaming
+-- lib/*.ml Context, Hooks, Runtime, etc.
Anything outside this repository — multi-process coordination, repo-wide task queues, dashboards, persistent shared state — is the responsibility of an external coordinator, not of OAS. OAS deliberately knows nothing about any specific coordinator.
| Module | Role |
|---|---|
Types |
Domain types: message, role, stop_reason, content_block, SSE events |
Provider |
LLM endpoint abstraction (Local / Anthropic / OpenAICompat / Ollama) |
Api |
HTTP client: create_message (sync) + create_message_stream (SSE) |
Agent |
Multi-turn agent loop with automatic tool_use handling (abstract Agent.t) |
Tool / Tool_set |
Tool definition, JSON Schema generation, O(1) lookup |
Builder |
Fluent API for agent construction with build_safe validation |
Hooks |
Generic lifecycle callbacks; PreToolUse accepts an exact caller-owned Continue or Block decision |
Guardrail_llm |
Adapts caller-injected typed judge closures; no fixed risk taxonomy or string parser |
Context |
Cross-turn shared state (scoped key-value store, Yojson.Safe.t values) |
Error / Error_domain |
2-level structured errors: 8 domain variants + Internal, poly-variant mapping |
Log |
Structured logging with level filtering and composable sinks |
Mcp |
MCP client (NDJSON-over-stdio, server lifecycle, paginated tool listing) |
Streaming |
Multi-provider SSE parsing (Anthropic + OpenAI-compatible) |
Pipeline |
6-stage turn pipeline with Provider_intf routing |
Contract |
Prompt/context composition: instruction layers, triggers, skills |
Plan |
Goal decomposition with dependency DAG and re-planning |
Not all modules are equally stable. Use this to gauge risk when depending on a module.
The public facade modules below carry explicit @stability and @since
markers; unannotated modules are Internal by default.
For the current classification policy, see docs/api-stability.md.
Stable -- safe to depend on, breaking changes require a major version bump:
Types, Error, Agent, Builder, Tool, Tool_set, Hooks, Provider, Raw_trace, Checkpoint, Checkpoint_store, Context
Evolving -- API may change between minor versions:
Streaming, Structured, Runtime
CDAL proof artifacts are no longer exposed as public OCaml modules from OAS. Use the versioned JSON schema catalog for downstream proof-bundle artifacts.
Internal -- implementation details with no compatibility promise:
lib/agent/* submodules, lib/protocol/*, lib/llm_provider/* backends, parser/transport helpers, and other implementation-specific support modules
(* Simple handler *)
let calc_tool =
Tool.create ~name:"calculator"
~description:"Evaluate a math expression"
~parameters:[{
Types.name = "expression";
description = "The math expression";
param_type = String; required = true;
}]
(fun args ->
let open Yojson.Safe.Util in
match args |> member "expression" |> to_string_option with
| Some expr ->
Ok { Types.content = Printf.sprintf "Result: %s" expr; _meta = None }
| None ->
Error
{ Types.message = "missing expression"
; recoverable = false
; error_class = None
})
(* Context-aware handler *)
let counter_tool =
Tool.create_with_context ~name:"counter"
~description:"Increment and return counter"
~parameters:[]
(fun ctx _input ->
let n = match Context.get ctx "count" with
| Some (`Int n) -> n + 1 | _ -> 1 in
Context.set ctx "count" (`Int n);
Ok { Types.content = string_of_int n; _meta = None })let my_hooks = { Hooks.empty with
pre_tool_use = Some (function
| Hooks.PreToolUse { tool_name; _ } ->
Printf.printf "Calling: %s\n" tool_name;
Hooks.Continue
| _ -> Hooks.Continue);
}OAS sends the caller-supplied Tool_set to the provider unchanged. A product
that needs tool exposure policy applies its own typed gate before constructing
the agent. A caller that has already settled an external-effect decision may
return Hooks.Block reason from PreToolUse; OAS does not own approval,
judging, risk levels, or HITL orchestration.
scripts/dune-local.sh build <target>
make test
# Full CI parity only when explicitly needed
scripts/dune-local.sh build @all
scripts/dune-local.sh runtest
# Coverage report
make coverage
# Integration tests (requires local LLM server)
LLAMA_LIVE_TEST=1 dune exec ./test/test_local_llm.exe
LLAMA_LIVE_TEST=1 dune exec ./test/test_streaming_e2e.exe
# Run examples
dune exec examples/basic_agent.exe
dune exec examples/review_agent.exe -- jeong-sik/oas 123- HTTP response body limit: 10MB.
- SSE streaming uses real-time event callbacks. OAS performs one provider attempt; any later caller-owned attempt starts outside the completed stream.
- The tool-use loop continues until the provider returns a terminal response or the caller cancels the fiber.
- Runs on a single Eio domain. No multi-core parallelism.
- Prompt caching tracks
cache_creation_input_tokensandcache_read_input_tokensin both streaming and non-streaming modes (since v0.4.0).
OAS is a single-process, single-Eio-domain agent runtime. The following concerns are explicitly out of scope and belong to a downstream consumer or a separate library.
| What | Owner | Why not here |
|---|---|---|
| Worktree / filesystem coordination | External coordinator | Multi-process file locking and git worktree lifecycle are coordinator concerns, not SDK concerns. |
| MCP transport implementation | mcp-protocol-sdk |
OAS consumes mcp_protocol as an opam dependency. Transport details (NDJSON framing, stdio management) live there. |
| Operator dashboard / visibility | External coordinator | Real-time agent status, workspace views, and supervisor dashboards belong to a layer that aggregates across processes. |
| Repo-level task and shared-state management | External coordinator | Task queues, claim semantics, and shared workspace state are coordination-plane concepts that span multiple agents and sessions. |
| Workflow scheduling / SaaS isolation | Embedding application | Cron triggers, tenant isolation, and multi-user access control are problems for the system that embeds OAS. |
| Long-term persistence / vector storage | Embedding application | Session state, memory backends, and embedding indexes are injected via callbacks, not owned by the SDK. |
If you find yourself pulling one of these responsibilities into agent_sdk, that is a signal the change belongs in a different repository. OAS does not name any specific downstream coordinator on purpose: anyone wiring OAS into a coordination layer should be free to make their own architectural choices.
The authoritative version string lives in lib/sdk_version.ml (mirrored in dune-project and agent_sdk.opam). Do not trust a number written into this README — it will drift.
We follow semver intent within the 0.x series:
- 0.x.0: May contain breaking changes with migration guide in
CHANGELOG.md. - 0.x.y (y > 0): Additive features and bug fixes only.
These are intent-level commitments on a personal project, not contractual guarantees.
See CONTRIBUTING.md for development setup and code style.