052591281d
- 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
325 lines
10 KiB
Markdown
325 lines
10 KiB
Markdown
# 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
|
|
|
|
```python
|
|
# 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
|
|
|
|
```json
|
|
{
|
|
"status": "ok",
|
|
"adapters": {
|
|
"stub": "stub",
|
|
"local_echo": "real",
|
|
"openai": "stub",
|
|
"cloudcode": "stub",
|
|
"agent": "real"
|
|
}
|
|
}
|
|
```
|
|
|
|
### POST /runs
|
|
|
|
```bash
|
|
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):
|
|
```json
|
|
{
|
|
"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:**
|
|
```json
|
|
{"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:**
|
|
```bash
|
|
# 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):**
|
|
```bash
|
|
export OLLAMA_MODEL=qwen2.5:7b # default
|
|
export OLLAMA_HOST=... # auto-resolved (0.0.0.0 → http://localhost:11434)
|
|
```
|
|
|
|
**Health endpoint shows current mode:**
|
|
```json
|
|
{
|
|
"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
|
|
|
|
```bash
|
|
# 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
|