Skip to content

Agent Mode

Agent mode lets AI coding assistants and automation tools drive the Olympix CLI programmatically through a structured JSON protocol over stdin/stdout.


When --agent (or -am) is passed to any command, the CLI:

  • Emits structured JSON events to stdout, one JSON object per line (NDJSON)
  • Reads JSON commands from stdin, one per line
  • Writes structured results to .opix/agent/ in the workspace directory
  • Suppresses interactive prompts and TUI rendering
  • Routes all human-readable log output to stderr, keeping stdout a clean JSON stream

This makes it possible for tools like Claude Code, Cursor, GitHub Copilot, or custom scripts to run Olympix analyses, respond to prompts, and consume results without human interaction.


Add --agent (or -am) to any command:

Terminal window
# BugPocer in agent mode
olympix bug-pocer --agent
# Unit testing in agent mode
olympix unit-testing --agent
# Mutation testing in agent mode
olympix mutation-testing --agent
# Fuzz testing in agent mode
olympix generate-fuzz-tests -p src/Vault.sol --agent
olympix connect-fuzz-session -s <session-id> --agent
# Static analysis in agent mode
olympix static-analysis --agent
# List all sessions (agent mode only)
olympix sessions --agent

tui, theme, login, login-sso and the org-* commands have no agent protocol — they ignore --agent and stay interactive.

Alternatively, set the environment variable:

Terminal window
export OLYMPIX_AGENT_MODE=1
olympix bug-pocer

Every line the CLI writes to stdout is a JSON object with this shape:

{
"event": "<event_type>",
"data": { },
"actions": ["action1", "action2"]
}
FieldTypeDescription
eventstringEvent type identifier
dataobject, string, or nullEvent-specific payload
actionsstring[] or nullValid actions the agent may send next

Send one JSON object per line to stdin:

{
"action": "<action_name>",
"data": { }
}
FieldTypeDescription
actionstringThe action to perform (must be one of the advertised actions)
dataobject or nullAction-specific payload

disconnect is always a valid action at any prompt — it gracefully closes the connection and exits.

EventDescriptionData
progressStatus update{ "message": "...", "percent": 42 }
errorError occurred{ "message": "...", "expected_actions": [...] }
completedOperation finished with nothing further to doA message string (e.g. no eligible files / all contracts mercy-ruled)

Both analyze and static-analysis run a fresh scan in agent mode. static-analysis skips its interactive session picker rather than reconnecting to a previous run, so the two commands behave identically here.

Terminal window
olympix analyze -w . --agent
olympix static-analysis -w . --agent

Event: findings_ready — reuses the BugPocer findings shape, with the verdict and PoC fields defaulted to "n/a" and session_id null:

{
"event": "findings_ready",
"data": {
"session_id": null,
"findings": [
{
"id": "f1",
"title": "unchecked-return-value",
"severity": "High",
"description": "...",
"affected_code": "...",
"file_path": "src/Vault.sol",
"line_number": 42
}
]
}
}

The CLI exits 0 straight after emitting the findings — no action is required. Findings you have ignored in opix.config.json are filtered out before the event is emitted. A workspace with no Solidity files emits an error event and exits non-zero.


BugPocer’s multi-stage pipeline maps to the following event/action sequence.

bug-pocer starts at session selection. start-bp-session skips sessions_list/new_session and begins directly at step 2 (scope_review, or diff_review in diff mode); the rest of the flow is identical and it accepts the same --diff-base/--diff-target flags.

Event: sessions_list

{
"event": "sessions_list",
"data": {
"sessions": [
{ "id": "abc-123", "title": "My Scan", "status": "ValidationRequested", "created_at": "...", "error_message": null }
]
},
"actions": ["new_session", "connect_session", "disconnect"]
}

Actions:

  • new_session with optional { "title": "My scan" } — start a new scan
  • connect_session with { "session_id": "abc-123" } — reconnect to an existing session

Event: context_cache_review — emitted only when a prior context matches this codebase and --rebuild-context was not passed.

{
"event": "context_cache_review",
"data": {
"match_type": "exact",
"source_session_id": "def-456",
"cached_at": "2026-07-01T12:00:00Z",
"overlap_percent": 100,
"changed_files": [],
"changed_files_total": 0,
"summary": {}
},
"actions": ["reuse_context", "rebuild_context", "disconnect"]
}

Actions:

  • reuse_context — reuse the cached context (exact match) or seed context building from it (partial match)
  • rebuild_context — ignore the cache and build a fresh context

match_type is exact (identical source fingerprint) or partial (overlap_percent ≥ 80). Closing stdin without answering defaults an exact match to reuse. See Context Cache for the rules.

Event: scope_review

{
"event": "scope_review",
"data": {
"contracts": [
{ "name": "Vault", "file": "src/Vault.sol", "functions": [{ "name": "withdraw", "visibility": "public" }] }
],
"libraries": []
},
"actions": ["select_scope", "confirm_all", "disconnect"]
}

Actions:

  • confirm_all — scan the full inferred scope
  • select_scope with { "exclude_contracts": [...], "exclude_libraries": [...], "exclude_functions": [...], "additional_docs": { "notes": "...", "links": [...] } } — narrow the scope

Event: validation_item — sent one at a time

{
"event": "validation_item",
"data": {
"key": "identity",
"name": "Project Identity",
"confidence": 85,
"content": "DeFi lending protocol...",
"options": [
{ "id": "opt1", "label": "Alternative 1", "value": "..." }
],
"current": 1,
"total": 7
},
"actions": ["confirm_item", "reject_item", "select_option", "disconnect"]
}

Actions:

  • confirm_item — accept the inference
  • reject_item with { "explanation": "custom correction" } — provide a correction
  • select_option with { "option_id": "opt1" } — pick a suggested alternative

Event: security_question

{
"event": "security_question",
"data": {
"question_id": "q1",
"category": "trust_boundaries",
"is_required": true,
"priority": 1,
"question_text": "Who are the trusted operators?",
"context": null,
"suggested_answers": [
{ "id": "a1", "label": "Only owner", "value": "...", "show_follow_up_ids": [] }
],
"current": 1,
"total": 5,
"is_follow_up": false,
"parent_question_id": null
},
"actions": ["select_answer", "custom_answer", "skip_question", "disconnect"]
}

Actions:

  • select_answer with { "question_id": "q1", "answer_id": "a1" } — pick a suggested answer
  • custom_answer with { "question_id": "q1", "answer": "free text" } — provide your own answer
  • skip_question — skip an optional question

Event: additional_docs_prompt

{
"event": "additional_docs_prompt",
"data": { "message": "Attach additional documentation..." },
"actions": ["submit_docs", "skip_docs", "disconnect"]
}

Actions:

  • submit_docs with { "notes": "...", "links": [...] } — submit docs and start the scan
  • skip_docs — start the scan without additional docs

Event: initial_scan_completed

{
"event": "initial_scan_completed",
"data": { "session_id": "abc-123", "message": "Scan complete", "total_scan_cost": "150 credits" }
}

Event: findings_ready

{
"event": "findings_ready",
"data": {
"session_id": "abc-123",
"findings": [
{
"id": "f1",
"title": "Reentrancy in withdraw()",
"severity": "High",
"description": "...",
"affected_code": "...",
"file_path": "src/Vault.sol",
"line_number": 42,
"bugpocer_verdict": "true_positive",
"user_verdict": "unreviewed",
"user_verdict_reason": null,
"effective_verdict": "true_positive",
"confidence_score": 90,
"poc_summary": "...",
"poc_content": "..."
}
]
},
"actions": ["set_verdict", "generate_pdf", "save_pocs", "save_findings_md", "disconnect"]
}

When findings arrive, the CLI auto-downloads the PoC exploit files and the split findings markdown to .opix/agent/<session-id>/ (default filter: true positives + unverified). The actions let you re-export or query:

  • save_pocs — re-export PoCs → pocs_saved { "session_id", "saved_count", "output_path" }
  • save_findings_md — re-export markdown → findings_saved { "session_id", "files": [{ "category", "count", "path" }] }
  • generate_pdf — generate the PDF report → pdf_generated { "session_id", "pdf_path" }
  • set_verdict with { "finding_id": "f1", "verdict": true, "reason": "confirmed" } — record your user verdict (true = true positive, false = false positive, null = clear to unreviewed) → verdict_set

kill-bp-session terminates an active session. It is a one-shot command — the session ID is passed as a flag and no stdin input is needed:

Terminal window
olympix kill-bp-session -s <session-id> --agent

Event: session_killed

{
"event": "session_killed",
"data": { "session_id": "abc-123", "was_running": true }
}

was_running is false when the session was not running (e.g. already completed). A missing session ID or no acknowledgment within 30 seconds emits an error event and exits non-zero.


unit-testing, mutation-testing, generate-unit-tests and generate-mutation-tests share the session / file-selection flow. Fuzz generation follows a different, dispatch-only flow — see Fuzz Test Generator Agent Protocol.

sessions_list (actions new_session, connect_session, disconnect) works as above. Starting a new session emits:

Event: file_selection

{
"event": "file_selection",
"data": {
"command": "generate-unit-tests",
"files": ["src/Token.sol", "src/Vault.sol"],
"max_files": 10
},
"actions": ["select_files"]
}

Respond with select_files and { "selected": ["src/Token.sol"] }.

Generation runs asynchronously and results are delivered by email; the CLI confirms dispatch with:

Event: results_ready

{
"event": "results_ready",
"data": { "type": "unit_test", "session_id": "abc-123", "message": "unit test generation started. Check email for results." },
"actions": ["disconnect"]
}

If a run has nothing to generate (e.g. the selected contracts have no matching OlympixUnitTest test file, or all subject contracts were mercy-ruled), the CLI emits a terminal completed event with an explanatory message instead — a valid, non-error outcome.

Reconnecting to a finished session (connect_session) fetches and downloads results:

Event: unit_test_results

{
"event": "unit_test_results",
"data": {
"session_id": "abc-123",
"total_files": 4,
"successful_files": 3,
"branches_coverage": 72.5,
"test_files": [
{ "subject_contract": "Vault", "subject_path": "src/Vault.sol", "test_contract": "VaultTest", "test_path": "test/Vault.t.sol", "has_new_tests": true, "coverage_before": 40.0, "coverage_after": 72.5, "passed": 12, "failed": 0 }
]
}
}

Event: mutation_test_results

{
"event": "mutation_test_results",
"data": {
"session_id": "abc-123",
"total_mutations": 50,
"killed": 42,
"survived": 8,
"score_percentage": 84,
"mutations": [
{ "file": "src/Vault.sol", "line": 42, "original": "...", "mutated": "...", "killed": true, "broken_tests": [] }
]
}
}

The generated .t.sol test files are written into the workspace automatically.

generate-unit-tests --list --agent emits list_contracts { "contracts": [{ "index", "name", "path" }] } and exits.


Fuzz runs are long-lived. generate-fuzz-tests only dispatches the run and returns a session ID — full results are emailed and can be pulled back later with connect-fuzz-session.

Terminal window
# Dispatch a run (returns a session_id; results arrive by email)
olympix generate-fuzz-tests -w . -p src/Vault.sol --agent
# List your fuzz sessions
olympix list-fuzz-sessions --agent
# Fetch a finished session's summary (+ optional PDF report)
olympix connect-fuzz-session -s <session-id> --agent
# Session manager: list, then reconnect, in one process
olympix fuzz-testing -w . --agent

generate-fuzz-tests emits a progress event carrying the new session ID, then a terminal completed event:

{
"event": "completed",
"data": { "type": "fuzz_test", "session_id": "abc-123", "message": "Fuzz generation started; results pending." }
}

connect-fuzz-session (and connect_session from a session list) emits a summary of the finished run:

Event: fuzz_test_results

{
"event": "fuzz_test_results",
"data": {
"session_id": "abc-123",
"contracts": 3,
"strategies": 7,
"test_cases": 128,
"exploit_test_cases": 2
},
"actions": ["generate_report", "disconnect"]
}
  • generate_report — render the PDF report → pdf_generated { "session_id", "pdf_path" }
  • disconnect — exit without generating a report

If the run has not finished yet, the CLI emits results_ready { "type": "fuzz_test", "session_id", "message": "Results not ready yet…" } instead and exits.

list-fuzz-sessions and fuzz-testing both emit sessions_list (actions new_session, connect_session, disconnect) and then follow the fetch flow above once you send connect_session.


In agent mode, the CLI writes structured results to .opix/agent/ within the workspace:

.opix/agent/
├── <session-id>/ # BugPocer, per session ("pending/" until the ID is known)
│ ├── scope.json # Scope review data
│ ├── diff.json # Diff review data (diff mode)
│ ├── context-cache.json # Context cache review data
│ ├── report.json # Initial scan report
│ ├── findings.json # Findings (mirrors findings_ready)
│ └── qa.json # Q&A exchange history
├── bug-pocer/
│ └── sessions.json # Session list
├── unit-tests/
│ ├── sessions.json # Session list
│ ├── contracts.json # Available contracts
│ └── results.json # Test results
├── mutation-tests/
│ ├── sessions.json # Session list
│ └── results.json # Test results
└── fuzz-tests/
├── sessions.json # Session list
└── results.json # Fuzz run summary

Files are written atomically (temp file + rename) and use snake_case JSON.


The sessions command is agent-mode-only and returns active sessions across all services in a single response:

Terminal window
olympix sessions --agent

Event: all_sessions — sessions grouped per service, as the arrays bug_pocer, unit_tests, mutation_tests, fuzz_tests and static_analysis. Each entry has id, title, status and created_at.


Errors are emitted as error events:

{
"event": "error",
"data": {
"message": "Invalid data payload: ...",
"expected_actions": ["select_scope", "confirm_all", "disconnect"]
}
}

The expected_actions field tells you what the CLI is still waiting for. Re-send a valid action to continue — a malformed or invalid action is reported and re-prompted rather than terminating the session.

While stdin stays open, the CLI does not give up on a slow agent: on an internal read timeout it re-emits the pending event and keeps waiting. It only ends a stage when it receives a valid action or when stdin is closed (EOF), at which point it disconnects and exits.

Send { "action": "disconnect" } at any prompt to gracefully close the connection and exit. This is always a valid action.


Terminal window
# Newline-delimited commands piped to stdin
olympix bug-pocer --agent <<'EOF'
{"action":"new_session"}
{"action":"confirm_all"}
{"action":"confirm_item"}
{"action":"confirm_item"}
{"action":"submit_docs","data":{"notes":"","links":[]}}
EOF

The CLI emits context_cache_review (if a prior context matches), then scope_review (or diff_review in diff mode), then a validation_item for each inference, then (optionally) security_questions and the additional_docs_prompt, and finally initial_scan_completed / findings_ready. Respond to each with one of its advertised actions.


If you encounter any issues or have questions, reach out:

Email: contact@olympix.ai