feat(engine): the turn runner — prompt builder, sandbox tools, agent loop

Phase 2 of TODO-agent-2: one employee, one turn, real tools in the sandbox.

- engine/prompt.py: the stable, cache-friendly prefix in the plan's order
  (rules → role protocol → toolkit → app profile → memory), a task brief
  capped at 1200 chars, role-specific stage context (spec for the developer,
  the diff only for the reviewer, test output for the tester) and a hard
  prompt cap that drops the oldest tool results first.
- engine/tools.py: READ/WRITE/EDIT/LIST_DIR/SEARCH/RUN executed inside the
  sandbox through SandboxProviderPort, rooted at the worktree's sandbox path.
  Path escapes, .. and .git are refused; git push/remote/config are refused
  with "the platform does git"; RUN clamps its timeout (120s default, 600s
  max) and keeps the tail of its output; every execution writes a tool_calls
  row so the approvals UI shows real work.
- engine/runner.py: the loop — model call, parse tags, execute tool tags,
  feed results back — until a terminal tag, the tool cap, the turn timeout or
  a cancellation. The employee's model is checked against ModelGrantPort
  before the first call (ungranted → error_kind=auth, no spend), errors are
  classified transient vs fatal, and every model call writes usage events
  through the one shared record_model_usage path (extracted from
  WorkforceService so manager chat counts too). Writes engine_turns, run_steps
  and tool_calls.
- engine/events.py: RunFeed batches run.log (≤1 event / 500 ms / stream, ≤200
  lines) and emits run.tool and task.gate through the generic envelope.
- tests: engine_fakes.py (ScriptedModel, FakeSandbox, FakeGrants,
  RecordingMemory) plus 54 new tests in test_engine_runner,
  test_engine_prompt and test_engine_tools.

Suite: 648 passed, ruff clean, mypy clean.
This commit is contained in:
mjalmoudy94
2026-10-14 16:32:52 +03:30
parent 9c0b911a00
commit c6488bfadf
13 changed files with 2712 additions and 19 deletions
+11
View File
@@ -148,6 +148,17 @@ class Settings(BaseSettings):
engine_max_cycles: int = 6
engine_max_employees: int = 20
engine_tick_seconds: float = 10.0
#: Hard cap on a turn's total prompt characters. The builder truncates the
#: oldest tool results first (never the protocol, never the task brief).
engine_prompt_char_cap: int = 24000
#: How much of one tool's output is fed back to the model, and how much of
#: a reply is stored in ``engine_turns.reply_excerpt``.
engine_tool_output_chars: int = 8000
engine_reply_excerpt_chars: int = 4000
#: Real-time log batching (PLAN-v2 §5): one event per stream per interval,
#: at most this many lines per event.
engine_log_flush_seconds: float = 0.5
engine_log_max_lines_per_event: int = 200
# <<< agent-2
# >>> agent-3 (delivery & infra): add settings ONLY between these markers