Files
Anton 052591281d Initial clean version - Agent Maxim
- Complete agent system with planning and execution
- Bridge API with FastAPI server
- WhatsApp-style mobile dashboard
- Enhanced web search with query improvement
- Security guards and policy checks
- Comprehensive documentation
2026-04-09 18:07:43 +03:00

10 KiB

Bridge V1.2 — Agent-Connected Orchestration

One-shot orchestration with a real agent backend. POST /runs with adapter="agent" executes the full agent pipeline and returns the final result.

Run Lifecycle

POST /runs  {"goal": "...", "adapter": "agent"}
  │
  ├─ status: created     → state.json written, run_created logged
  ├─ instruction built   → "Task: <goal>\n\nPlease complete..."
  │                        instruction_generated logged
  ├─ policy check        → blocked? → status=failed, return
  ├─ status: running     → task_sent logged
  ├─ agent_adapter.execute(instruction, bridge_run_id=rid)
  │     └─ subprocess: python agent/main.py --goal <instruction> --run-id agent-<rid>
  │     └─ reads agent/workspace/runs/agent-<rid>/state.json
  │     response_received logged (mode="real", exit_code, agent_run_id)
  └─ exit_code==0 → status=done | else → status=failed
     run_finished logged (agent_final_status, agent_run_id)

Status transitions: created → running → done | failed Status on return from POST /runs: always done or failed, never running.

Run ID Mapping

Bridge run_id  : br-flow-01
Agent  run_id  : agent-br-flow-01

Bridge state   : bridge/runs/br-flow-01/state.json
Agent state    : agent/workspace/runs/agent-br-flow-01/state.json

Every bridge run maps to exactly one agent run. Both are traceable by ID.

Project Structure

bridge/
  app.py                         FastAPI entry point (v1.2.0)
  config.py                      Env config
  api/routes.py                  POST /runs, GET /runs/{id}, GET /health
  core/
    run_manager.py               One-shot execute_run() with metadata extraction
    instruction_builder.py       Goal → instruction (deterministic)
    state_store.py               state.json read/write
    audit.py                     JSONL append-only logger
  adapters/
    __init__.py                  Registry: stub, local_echo, openai, cloudcode, agent
    base.py                      AdapterResult(response,error,exit_code,mode,metadata)
    stub_adapter.py              Always-available stub
    local_echo_adapter.py        Real subprocess (Python child process)
    openai_adapter.py            Real if OPENAI_API_KEY, stub otherwise
    cloudcode_adapter.py         Stub — transport unavailable
    agent_adapter.py             REAL — subprocess call to agent/main.py
  policy/guard.py                Prompt validation
  models/schemas.py              Pydantic schemas (RunResponse includes agent_* fields)
  runs/<run_id>/
    state.json
    audit.jsonl

Agent Adapter Design

# bridge/adapters/agent_adapter.py

_AGENT_MAIN = bridge/../agent/main.py

def execute(prompt: str, bridge_run_id: str = None) -> AdapterResult:
    agent_run_id = f"agent-{bridge_run_id}"
    proc = subprocess.run(
        [sys.executable, str(_AGENT_MAIN),
         "--goal", prompt, "--run-id", agent_run_id],
        capture_output=True, text=True, timeout=60, shell=False,
        cwd=str(_AGENT_DIR),
    )
    # Read agent/workspace/runs/{agent_run_id}/state.json
    # Extract agent_final_status
    return AdapterResult(
        response=proc.stdout,
        error=... if proc.returncode != 0 else None,
        exit_code=proc.returncode,
        mode="real",
        metadata={"agent_run_id": ..., "agent_final_status": ..., "agent_state_path": ...}
    )

Goal routing: The instruction passed as --goal contains the original goal as a substring. The agent's deterministic planner uses keyword in goal.lower() matching, so all keywords resolve correctly through the wrapper text.

API

GET /health

{
  "status": "ok",
  "adapters": {
    "stub": "stub",
    "local_echo": "real",
    "openai": "stub",
    "cloudcode": "stub",
    "agent": "real"
  }
}

POST /runs

curl -X POST http://localhost:8000/runs \
  -H "Content-Type: application/json" \
  -d '{"goal": "test flow pwd to file", "adapter": "agent"}'

Returns final state (status = done or failed):

{
  "run_id": "br-flow-01",
  "status": "done",
  "adapter": "agent",
  "adapter_mode": "real",
  "exit_code": 0,
  "agent_run_id": "agent-br-flow-01",
  "agent_final_status": "done",
  "agent_state_path": ".../agent/workspace/runs/agent-br-flow-01/state.json"
}

GET /runs/{id}

Returns persisted bridge state.

Adapter Modes

Adapter Mode Execution
stub stub Python string echo
local_echo real subprocess.run([python, -c, echo_script])
openai real/stub Real if OPENAI_API_KEY set, stub otherwise
cloudcode stub Transport unavailable in V1.2
agent real subprocess.run([python, agent/main.py, --goal, ...])

Storage Layout

bridge/runs/<run_id>/state.json  fields:
  run_id, goal, status, adapter, adapter_mode,
  instruction, response, error, exit_code,
  agent_run_id, agent_final_status, agent_state_path,
  created_at, updated_at

agent/workspace/runs/<agent_run_id>/state.json  fields:
  run_id, goal, plan, history, status, current_step_id,
  started_at, updated_at

Audit Events (agent adapter)

run_created          goal + adapter="agent"
instruction_generated  instruction_len
task_sent            adapter="agent", instruction_len
response_received    mode="real", exit_code, response_len, agent_run_id
run_finished         status, adapter_mode="real", agent_run_id, agent_final_status

Controller Loop V1 (Bridge V1.3)

Architecture

POST /controller/runs  {"goal": "..."}
  │
  ├─ ControllerState created, controller_audit.jsonl opened
  ├─ Loop (max 3 iterations):
  │    ├─ decide(goal, iteration, last_result)
  │    │    ├─ stub: deterministic rules
  │    │    └─ real: OpenAI call → JSON parse → fallback to stub
  │    ├─ if action == "stop": break
  │    ├─ RunManager.execute_run(goal, adapter="agent")
  │    │    └─ bridge run → agent subprocess → result
  │    └─ observe result → update controller state
  └─ final_status persisted, controller_finished logged

Loop Stop Conditions

  1. decision.action == "stop" — decision layer says stop
  2. iteration >= MAX_ITERATIONS (3) — hard iteration cap
  3. consecutive_failures >= 2 — repeated failure guard

Stub Decision Rules

Condition Action Reason
No previous run (iteration 0) run_agent First execution
Last run status=done + agent_done stop Success
Last run status=failed stop No controller-level retry
Iteration approaching max stop Safety cap

Ollama Decision Rules (V1.3 — default)

Ollama is the primary decision backend. If Ollama is unreachable at startup, falls back to stub automatically.

Decision flow:

decide(goal, iteration, last_result, recovery_attempted)
  │
  ├─ DECISION_MODE=stub   → _decide_stub()  (Ollama unreachable at startup)
  └─ DECISION_MODE=ollama → ollama_adapter.execute(prompt, temperature=0.1, max_tokens=150)
       ├─ connection error / empty → ollama-fallback  (stub logic used)
       ├─ bad/truncated JSON       → ollama-fallback  (stub logic used)
       ├─ invalid action/goal      → ollama-fallback  (stub logic used)
       └─ valid JSON               → Decision(mode="ollama", parsed_success=True)

Prompt context sent:

goal, iteration/max_iterations, last_status, last_agent_status, recovery_attempted

Expected JSON output:

{"action": "run_agent"|"stop", "goal": "...", "reason": "..."}

Safety guard (before accepting):

  • action must be "run_agent" or "stop"
  • goal must be a string ≤ 500 chars
  • markdown fences and leading prose stripped automatically

Decision modes in state/audit:

mode meaning
ollama Ollama response parsed successfully
ollama-fallback Ollama called but response invalid — stub used
stub Ollama unreachable at startup — deterministic rules

Ollama setup:

# Install: https://ollama.com — already running on Windows
# Default model
ollama pull qwen2.5:7b
# Optional fallback
ollama pull llama3.2:3b

Configuration (env vars):

export OLLAMA_MODEL=qwen2.5:7b   # default
export OLLAMA_HOST=...           # auto-resolved (0.0.0.0 → http://localhost:11434)

Health endpoint shows current mode:

{
  "status": "ok",
  "decision_backend": "ollama",
  "ollama_model": "qwen2.5:7b",
  "adapters": {"ollama": "real", ...}
}

Run ID Traceability

controller_run_id  : ctrl-xxxx
  └─ bridge run_id : bri-ctrl-xxxx-i0   (iteration 0)
       └─ agent run_id : agent-bri-ctrl-xxxx-i0
  └─ bridge run_id : bri-ctrl-xxxx-i1   (iteration 1, if any)

Storage

bridge/runs/ctrl-xxxx/
  controller_state.json    — controller run, decisions, bridge results
  controller_audit.jsonl   — controller-level events

bridge/runs/bri-ctrl-xxxx-i0/
  state.json               — bridge run state
  audit.jsonl              — bridge run events

agent/workspace/runs/agent-bri-ctrl-xxxx-i0/
  state.json               — agent run with full step history
  audit.jsonl              — agent step events

Controller Audit Events

controller_started     goal, decision_mode, max_iterations
decision_made          iteration, action, reason, mode, parsed_success, fallback_used
bridge_run_started     iteration, bridge_run_id, goal
bridge_run_finished    iteration, status, agent_final_status, exit_code
recovery_considered    iteration, original_goal, recovery_applied, reason
recovery_applied       iteration, original_goal, rewritten_goal, rule_reason
recovery_skipped       iteration, original_goal, reason
controller_finished    final_status, iterations, linked_runs

API

# Start controller run
POST /controller/runs  {"goal": "test retry execution"}201  ControllerResponse (final state)

# Get controller run
GET  /controller/runs/{ctrl_id}200  ControllerResponse

V1.2 Limitations (agent/bridge layer)

  • instruction_builder is a string wrapper — not an LLM planner
  • agent planner is deterministic keyword-matching — not dynamic
  • bridge sends full instruction as --goal; agent's goal field in state.json shows the wrapper text (not the raw goal)
  • one step per bridge run (single agent invocation)
  • cloudcode adapter is stub-only
  • no authentication, no streaming, no retries