-
Notifications
You must be signed in to change notification settings - Fork 0
Feature/scalable agent loop #4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
47d0895
4cd195f
f656495
654e707
f101074
a9bf765
f2c0ccd
91ac83a
b900f1e
4625e8f
6eae080
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| # AGENTS.md | ||
|
|
||
| - Prefer minimal Java: records, switch expressions, `var`, fail-fast guards, and small methods. | ||
| - Keep nesting shallow. Extract private methods instead of stacking conditionals. | ||
| - Use built-in JDK features before adding framework code. | ||
| - Keep plugins and tools read-only by default unless mutation is explicitly required. | ||
| - Favor the smallest change that preserves tests and clarity. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| # CLAUDE.md | ||
|
|
||
| This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. | ||
|
|
||
| ## Build & Test Commands | ||
|
|
||
| ```bash | ||
| ./gradlew build # Build + run tests | ||
| ./gradlew test # Run all tests (JUnit 5 + Mockito) | ||
| ./gradlew jar # Build fat JAR at build/libs/openclaw-java.jar | ||
| ./gradlew test --tests "ai.openclaw.tool.CodeExecutionToolTest" # Run single test class | ||
| ./gradlew test --tests "ai.openclaw.tool.CodeExecutionToolTest.testBlocksDangerousCommands" # Single test method | ||
| ``` | ||
|
|
||
| Run the gateway: `java -jar build/libs/openclaw-java.jar gateway` | ||
|
|
||
| ## Architecture | ||
|
|
||
| This is an MVP Java port of [OpenClaw](https://github.com/openclaw/openclaw), a personal AI assistant. Java 21+ required (virtual threads). | ||
|
|
||
| ### Core Flow | ||
|
|
||
| `Main` → picocli CLI (`OpenClawCli`) → `GatewayCommand` starts all components: | ||
| 1. **ConfigLoader** reads `~/.openclaw-java/config.json` (or env vars `ANTHROPIC_API_KEY`, `GATEWAY_PORT`, `GATEWAY_AUTH_TOKEN`) | ||
| 2. **GatewayServer** — WebSocket server (Java-WebSocket lib) with JSON-RPC protocol and token-based auth | ||
| 3. **RpcRouter** dispatches methods (`gateway.health`, `agent.send`) to handlers | ||
| 4. **AgentExecutor** — agentic loop: sends messages to LLM, handles tool_use responses in a loop (max 10 iterations), persists all messages to session | ||
| 5. **ConsoleChannel** — stdin/stdout interactive channel for local use | ||
|
|
||
| ### Key Interfaces | ||
|
|
||
| - **`LlmProvider`** (`agent/`) — LLM backend interface with `complete()` and `completeWithTools()`. `AnthropicProvider` implements it using raw OkHttp calls to the Anthropic Messages API. | ||
| - **`Tool`** (`tool/`) — Agent tool interface: `name()`, `description()`, `inputSchema()` (JSON Schema), `execute(JsonNode)` → `ToolResult`. Implementations: `CodeExecutionTool`, `FileReadTool`, `FileWriteTool`, `WebSearchTool`. | ||
| - **`Channel`** (`channel/`) — Messaging channel interface. Only `ConsoleChannel` is implemented. | ||
|
|
||
| ### Session/Message Model | ||
|
|
||
| `SessionStore` holds in-memory sessions with JSONL file persistence. `Message` supports multiple roles: `user`, `assistant`, `system`, `assistant_tool_use` (assistant messages with tool_use content blocks), and `tool_result` (tool responses with `toolUseId`). The `AnthropicProvider` merges consecutive `tool_result` messages into a single `user` message per the Anthropic API contract. | ||
|
|
||
| ### Config | ||
|
|
||
| `OpenClawConfig` is a Jackson-deserialized POJO with nested `GatewayConfig` (port, authToken) and `AgentConfig` (provider, apiKey, model, systemPrompt). All config classes use `@JsonIgnoreProperties(ignoreUnknown = true)`. | ||
|
|
||
| ### Dependencies | ||
|
|
||
| Jackson (JSON), picocli (CLI), Java-WebSocket (gateway), OkHttp (HTTP client for Anthropic API), Logback/SLF4J (logging). Shared `ObjectMapper` via `Json.mapper()`. | ||
|
|
||
| ## Conventions | ||
|
|
||
| - Package root: `ai.openclaw` | ||
| - All JSON serialization goes through `ai.openclaw.config.Json.mapper()` singleton | ||
| - Tools define their own JSON Schema via `inputSchema()` method | ||
| - `CodeExecutionTool` has safety patterns: blocked patterns (dangerous commands) and warned patterns (risky commands) | ||
| - Gateway uses constant-time token comparison for auth |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,32 +1,32 @@ | ||
| FROM gradle:8-jdk21 AS build | ||
| FROM eclipse-temurin:21-jdk-alpine AS build | ||
| WORKDIR /app | ||
| COPY build.gradle.kts settings.gradle.kts ./ | ||
| COPY gradlew . | ||
| COPY gradle ./gradle | ||
| # Download dependencies first (cached layer) | ||
| RUN gradle dependencies --no-daemon || true | ||
| COPY build.gradle.kts settings.gradle.kts gradle.properties ./ | ||
| RUN ./gradlew dependencies --no-daemon || true | ||
| COPY src ./src | ||
| RUN gradle jar --no-daemon | ||
| RUN ./gradlew jar --no-daemon | ||
|
|
||
| FROM eclipse-temurin:21-jre-alpine | ||
| WORKDIR /app | ||
|
|
||
| # Install bash for CodeExecutionTool | ||
| RUN apk add --no-cache bash curl | ||
| RUN apk add --no-cache bash curl github-cli su-exec | ||
|
|
||
| # Create a non-root user with a dedicated workspace for tool execution | ||
| RUN addgroup -S openclaw && adduser -S openclaw -G openclaw \ | ||
| && mkdir -p /home/openclaw/workspace \ | ||
| && chown -R openclaw:openclaw /home/openclaw | ||
|
|
||
| COPY --from=build /app/build/libs/*.jar /app/openclaw.jar | ||
| COPY entrypoint.sh /app/entrypoint.sh | ||
|
|
||
| # Default gateway port | ||
| ENV GATEWAY_PORT=18789 | ||
| EXPOSE ${GATEWAY_PORT} | ||
|
|
||
| # Run as non-root user | ||
| USER openclaw | ||
| # Run as non-root user (entrypoint needs root briefly to import certs, then drops) | ||
| ENV HOME=/home/openclaw | ||
|
|
||
| ENTRYPOINT ["java", "-jar", "/app/openclaw.jar"] | ||
| ENTRYPOINT ["/bin/bash", "/app/entrypoint.sh"] | ||
| CMD ["gateway"] |
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
| @@ -0,0 +1,29 @@ | ||||
| #!/bin/bash | ||||
| set -e | ||||
|
|
||||
| # Import any custom CA certs mounted at /certs/*.crt into the JVM truststore. | ||||
| # Handles both single-cert PEM files and multi-cert PEM bundles. | ||||
| CACERTS="${JAVA_HOME}/lib/security/cacerts" | ||||
| if [ -d /certs ] && ls /certs/*.crt 2>/dev/null 1>/dev/null; then | ||||
| for bundle in /certs/*.crt; do | ||||
|
|
||||
| rm -f /tmp/cert-*.pem | ||||
| csplit -z -f /tmp/cert- -b '%03d.pem' "$bundle" \ | ||||
| '/-----BEGIN CERTIFICATE-----/' '{*}' 2>/dev/null 1>/dev/null || true | ||||
| i=0 | ||||
| for pem in /tmp/cert-*.pem; do | ||||
| [ -f "$pem" ] || continue | ||||
| alias="imported-$(basename "$bundle" .crt)-$i" | ||||
| keytool -importcert -noprompt \ | ||||
| -keystore "$CACERTS" \ | ||||
| -storepass changeit \ | ||||
| -alias "$alias" \ | ||||
| -file "$pem" 2>/dev/null || true | ||||
| rm -f "$pem" | ||||
| i=$((i+1)) | ||||
| done | ||||
| done | ||||
| fi | ||||
| fi | ||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 Extra The Root CauseThe shell structure is:
Verified with Impact: The Docker container will fail to start entirely because
Suggested change
Was this helpful? React with 👍 or 👎 to provide feedback. |
||||
|
|
||||
| exec su-exec openclaw java -Duser.home=/home/openclaw -jar /app/openclaw.jar "$@" | ||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| org.gradle.jvmargs=-Xmx512m |
Uh oh!
There was an error while loading. Please reload this page.