name: "HTTP API Server (AgentRuntime, port 9090)"
description: |
Verify the HTTP server (Goal I) runs correctly with AgentRuntime: health,
tools/openapi discovery, stateless /run, error envelopes, full session
lifecycle (create → message → multi-turn → detail → list → delete), SSE
streaming, and metrics.
Server lifecycle is owned by setup/teardown. The session id is shared
across cases via /tmp/http-sid (a fixture file), since ArgusAI's `save:`
cannot capture exec stdout (engine limitation, see 11-session-resume).
SSE assertion uses curl/grep — keeping the in-container network path
rather than migrating to native sse: avoids a behavioral change.
Rate limiting is exercised in 08b-http-rate-limit.yaml.
sequential: true
setup:
- name: "Kill any leftover server on port 9090"
ignoreError: true
exec:
container: recursive-e2e
command: |
pkill -f 'http --addr 0.0.0.0:9090' 2>/dev/null || true
sleep 1
echo cleaned
- name: "Reset state files (sid, sse-sid, sse-events, sess.json) + workspace artifacts"
ignoreError: true
exec:
container: recursive-e2e
command: |
rm -f /tmp/http-sid /tmp/sse-sid /tmp/sse-events.txt /tmp/sess.json /tmp/http-9090.log
rm -f /workspace/http-run.txt /workspace/session-turn1.txt /workspace/session-turn2.txt /workspace/sse-test.txt
echo reset
- name: "Start HTTP server in background on 9090"
exec:
container: recursive-e2e
command: |
RECURSIVE_RATE_LIMIT_BURST=200 \
RECURSIVE_RATE_LIMIT_RPM=6000 \
nohup recursive \
--api-base http://aimock:4010/v1 \
--api-key mock-key -m mock-chat \
http --addr 0.0.0.0:9090 \
> /tmp/http-9090.log 2>&1 &
sleep 2
echo started
- name: "Sanity: GET /health returns ok before running cases"
exec:
container: recursive-e2e
command: curl -sf http://localhost:9090/health
expect:
exitCode: 0
output:
contains: "ok"
cases:
- name: "GET /tools returns read_file in JSON array"
exec:
container: recursive-e2e
command: curl -sf http://localhost:9090/tools
expect:
exitCode: 0
output:
contains: "read_file"
- name: "GET /openapi.json returns OpenAPI spec"
exec:
container: recursive-e2e
command: curl -sf http://localhost:9090/openapi.json
expect:
exitCode: 0
output:
contains: "Recursive Agent API"
- name: "GET /sessions/:id returns 404 for non-existent session"
exec:
container: recursive-e2e
command: >
curl -s -o /dev/null -w 'status=%{http_code}'
http://localhost:9090/sessions/nonexistent-session-id-xyz
expect:
exitCode: 0
output:
contains: "status=404"
- name: "POST /sessions/:id/messages returns 404 for non-existent session"
exec:
container: recursive-e2e
command: >
curl -s -o /dev/null -w 'status=%{http_code}'
-X POST http://localhost:9090/sessions/nonexistent-session-id-xyz/messages
-H 'Content-Type: application/json'
-d '{"content":"hello"}'
expect:
exitCode: 0
output:
contains: "status=404"
- name: "GET /sessions/:id/events returns 404 for non-existent session"
exec:
container: recursive-e2e
command: >
curl -s -o /dev/null -w 'status=%{http_code}'
http://localhost:9090/sessions/nonexistent-session-id-xyz/events
expect:
exitCode: 0
output:
contains: "status=404"
- name: "POST /run (act + assert): finish_reason + messages + total_steps"
exec:
container: recursive-e2e
command: >
curl -sf -X POST http://localhost:9090/run
-H 'Content-Type: application/json'
-d '{"goal":"Write file http-run.txt with content run-ok","max_steps":5}'
expect:
exitCode: 0
output:
contains:
- "finish_reason"
- "messages"
- "total_steps"
- name: "POST /run actually wrote http-run.txt to workspace (defends against tool-not-found regression)"
file:
container: recursive-e2e
path: /workspace/http-run.txt
exists: true
contains: "run-ok"
- name: "POST /run with empty goal returns 400"
exec:
container: recursive-e2e
command: >
curl -s -o /dev/null -w status=%{http_code}
-X POST http://localhost:9090/run
-H 'Content-Type: application/json'
-d '{"goal":""}'
expect:
exitCode: 0
output:
contains: "status=400"
- name: "POST /sessions (act): create session, save id to /tmp/http-sid"
exec:
container: recursive-e2e
command: >
curl -sf -X POST http://localhost:9090/sessions
-H 'Content-Type: application/json'
-d '{"system_prompt":"You are a test assistant."}'
| tee /tmp/sess.json | jq -r .id > /tmp/http-sid
&& echo created=$(cat /tmp/http-sid)
expect:
exitCode: 0
output:
contains: "created="
- name: "POST /sessions/:id/messages (act): execute first turn"
exec:
container: recursive-e2e
command: >
SID=$(cat /tmp/http-sid)
&& curl -sf -X POST http://localhost:9090/sessions/$SID/messages
-H 'Content-Type: application/json'
-d '{"content":"Write file session-turn1.txt with content turn1"}'
| jq -r .role | xargs -I {} echo role={}
expect:
exitCode: 0
output:
contains: "role=assistant"
- name: "Session turn 1 actually wrote session-turn1.txt"
file:
container: recursive-e2e
path: /workspace/session-turn1.txt
exists: true
contains: "turn1"
- name: "GET /sessions/:id returns transcript with messages"
exec:
container: recursive-e2e
command: >
SID=$(cat /tmp/http-sid)
&& curl -sf http://localhost:9090/sessions/$SID
| jq '.messages | length' | xargs -I {} echo count={}
expect:
exitCode: 0
output:
contains: "count="
- name: "POST /sessions/:id/messages (act 2): second turn keeps prior context"
exec:
container: recursive-e2e
command: >
SID=$(cat /tmp/http-sid)
&& curl -sf -X POST http://localhost:9090/sessions/$SID/messages
-H 'Content-Type: application/json'
-d '{"content":"Write file session-turn2.txt with content turn2"}'
| jq -r .role | xargs -I {} echo role={}
expect:
exitCode: 0
output:
contains: "role=assistant"
- name: "Session turn 2 actually wrote session-turn2.txt (cross-turn tool-call regression check)"
file:
container: recursive-e2e
path: /workspace/session-turn2.txt
exists: true
contains: "turn2"
- name: "POST /sessions (act): create dedicated SSE session"
exec:
container: recursive-e2e
command: >
curl -sf -X POST http://localhost:9090/sessions
-H 'Content-Type: application/json'
-d '{"system_prompt":"You are a test assistant."}'
| jq -r .id > /tmp/sse-sid
&& echo "created=$(cat /tmp/sse-sid)"
expect:
exitCode: 0
output:
contains: "created="
- name: "GET /sessions/:id/events delivers `done` when a run completes"
exec:
container: recursive-e2e
command: |
SSE_SID=$(cat /tmp/sse-sid)
: > /tmp/sse-events.txt
curl -s -N "http://localhost:9090/sessions/$SSE_SID/events" \
> /tmp/sse-events.txt 2>/dev/null &
SSE_PID=$!
sleep 0.5
curl -sf -X POST "http://localhost:9090/sessions/$SSE_SID/messages" \
-H 'Content-Type: application/json' \
-d '{"content":"Write file sse-test.txt with content sse-ok"}' \
> /dev/null
sleep 0.5
kill $SSE_PID 2>/dev/null || true
wait $SSE_PID 2>/dev/null || true
grep -q "done" /tmp/sse-events.txt && echo "sse-received" || echo "sse-missing"
expect:
exitCode: 0
output:
contains: "sse-received"
- name: "SSE turn actually wrote sse-test.txt to workspace"
file:
container: recursive-e2e
path: /workspace/sse-test.txt
exists: true
contains: "sse-ok"
- name: "DELETE /sessions/:id removes the SSE session"
exec:
container: recursive-e2e
command: >
SSE_SID=$(cat /tmp/sse-sid)
&& curl -sf -X DELETE http://localhost:9090/sessions/$SSE_SID
&& echo cleaned
expect:
exitCode: 0
output:
contains: "cleaned"
- name: "GET /sessions lists sessions with message_count"
exec:
container: recursive-e2e
command: curl -sf http://localhost:9090/sessions
expect:
exitCode: 0
output:
contains: "message_count"
- name: "DELETE /sessions/:id removes session; subsequent GET returns 404"
exec:
container: recursive-e2e
command: >
SID=$(cat /tmp/http-sid)
&& curl -sf -X DELETE http://localhost:9090/sessions/$SID
&& CODE=$(curl -s -o /dev/null -w %{http_code}
http://localhost:9090/sessions/$SID)
&& echo after_delete=$CODE
expect:
exitCode: 0
output:
contains: "after_delete=404"
- name: "GET /metrics returns Prometheus counters"
exec:
container: recursive-e2e
command: curl -sf http://localhost:9090/metrics
expect:
exitCode: 0
output:
contains:
- "recursive_requests_total"
- "recursive_agent_runs_total"
- "recursive_agent_steps_total"
teardown:
- name: "Stop HTTP server on 9090"
ignoreError: true
exec:
container: recursive-e2e
command: |
pkill -f 'http --addr 0.0.0.0:9090' 2>/dev/null || true
echo stopped
- name: "Remove state fixtures and workspace artifacts"
ignoreError: true
exec:
container: recursive-e2e
command: |
rm -f /tmp/http-sid /tmp/sse-sid /tmp/sse-events.txt /tmp/sess.json /tmp/http-9090.log
rm -f /workspace/http-run.txt /workspace/session-turn1.txt /workspace/session-turn2.txt /workspace/sse-test.txt