fix: use server's model for agent ticks, add logging (#138)

fix: use server's model for agent ticks, add logging
This commit is contained in:
Jon Saad-Falcon
2026-03-26 16:04:31 -07:00
committed by GitHub
3 changed files with 120 additions and 39 deletions
+2 -17
View File
@@ -1280,7 +1280,7 @@ function InteractTab({ agentId, agentStatus }: { agentId: string; agentStatus: s
onKeyDown={(e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
handleSend('queued');
handleSend('immediate');
}
}}
placeholder="Send a message to this agent..."
@@ -1293,23 +1293,8 @@ function InteractTab({ agentId, agentStatus }: { agentId: string; agentStatus: s
disabled={sending || !input.trim()}
className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-sm cursor-pointer font-medium"
style={{ background: 'var(--color-accent)', color: '#fff', opacity: sending || !input.trim() ? 0.5 : 1 }}
title="Send immediately (interrupts agent)"
>
<Zap size={13} /> Immediate
</button>
<button
onClick={() => handleSend('queued')}
disabled={sending || !input.trim()}
className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-sm cursor-pointer"
style={{
background: 'var(--color-bg-secondary)',
color: 'var(--color-text)',
border: '1px solid var(--color-border)',
opacity: sending || !input.trim() ? 0.5 : 1,
}}
title="Queue message for next run"
>
<Send size={13} /> Queue
<Send size={13} /> Send
</button>
</div>
</div>
+46 -1
View File
@@ -203,6 +203,12 @@ class AgentExecutor:
if not model:
raise FatalError("No model configured for agent")
logger.info(
"Agent %s [%s]: using model=%s, engine=%s",
agent["name"], agent["id"],
model, type(engine).__name__,
)
# Optionally override model via router policy
router_policy_key = config.get("router_policy")
if router_policy_key and self._system:
@@ -247,6 +253,16 @@ class AgentExecutor:
input_text = f"{input_text}\n\nNew instructions:\n{user_msgs}"
for m in pending:
self._manager.mark_message_delivered(m["id"])
logger.info(
"Agent %s: delivering %d pending message(s)",
agent["name"], len(pending),
)
else:
logger.info(
"Agent %s: no pending messages, running with "
"instruction only",
agent["name"],
)
# Build AgentContext with memory results from FTS5 backend
from openjarvis.agents._stubs import AgentContext
@@ -298,7 +314,22 @@ class AgentExecutor:
pass # Don't break agent tick if memory retrieval fails
agent_ctx.memory_results = memory_results
return agent_instance.run(input_text, context=agent_ctx)
logger.info(
"Agent %s: calling agent.run() with %d chars input",
agent["name"], len(input_text),
)
_t0 = time.time()
result = agent_instance.run(input_text, context=agent_ctx)
_elapsed = time.time() - _t0
logger.info(
"Agent %s: agent.run() completed in %.1fs, "
"content_len=%d, turns=%d, tokens=%s",
agent["name"], _elapsed,
len(result.content or ""),
result.turns,
result.metadata.get("total_tokens", "?"),
)
return result
def _build_error_detail(self, error: AgentTickError) -> dict[str, Any]:
"""Build structured error detail for trace metadata."""
@@ -336,6 +367,12 @@ class AgentExecutor:
"""Update agent state after tick completion or failure."""
if error is None:
# Success
logger.info(
"Tick succeeded for agent %s in %.1fs, "
"response_len=%d",
agent_id, duration,
len(result.content or "") if result else 0,
)
self._manager.end_tick(agent_id)
self._manager.update_agent(agent_id, total_runs_increment=1)
@@ -381,6 +418,10 @@ class AgentExecutor:
"status": "ok",
})
elif isinstance(error, EscalateError):
logger.warning(
"Tick escalated for agent %s after %.1fs: %s",
agent_id, duration, error,
)
self._manager.end_tick(agent_id)
self._manager.update_agent(agent_id, status="needs_attention")
self._bus.publish(EventType.AGENT_TICK_ERROR, {
@@ -390,6 +431,10 @@ class AgentExecutor:
"duration": duration,
})
else:
logger.error(
"Tick failed for agent %s after %.1fs: %s",
agent_id, duration, error, exc_info=error,
)
self._manager.end_tick(agent_id)
self._manager.update_agent(agent_id, status="error")
# Write error detail to summary_memory so frontend can display it
+72 -21
View File
@@ -64,6 +64,24 @@ _BROWSER_SUB_TOOLS = {
}
class _LightweightSystem:
"""Minimal system facade for the executor — avoids rebuilding the
full JarvisSystem (which picks a random model from Ollama)."""
def __init__(self, engine: Any, model: str, config: Any = None):
self.engine = engine
self.model = model
self.config = config
self.memory_backend = None
def _make_lightweight_system(
engine: Any, model: str, config: Any = None,
) -> _LightweightSystem:
"""Build a minimal system from the server's existing state."""
return _LightweightSystem(engine, model, config)
def _ensure_registries_populated() -> None:
"""Ensure ToolRegistry and ChannelRegistry are populated.
@@ -452,7 +470,7 @@ def create_agent_manager_router(
return {"status": "idle"}
@agents_router.post("/{agent_id}/run")
async def run_agent(agent_id: str):
async def run_agent(agent_id: str, request: Request):
import threading
agent = manager.get_agent(agent_id)
@@ -473,28 +491,30 @@ def create_agent_manager_router(
status_code=409, detail="Agent is already running"
)
# Re-use the server's engine + model so we don't pick a
# random model from Ollama's list.
server_engine = getattr(request.app.state, "engine", None)
server_model = getattr(request.app.state, "model", "")
server_config = getattr(request.app.state, "config", None)
def _run_tick():
try:
from openjarvis.agents.executor import AgentExecutor
from openjarvis.core.events import get_event_bus
from openjarvis.system import SystemBuilder
executor = AgentExecutor(
manager=manager, event_bus=get_event_bus(),
)
try:
system = SystemBuilder().build()
executor.set_system(system)
except Exception as build_err:
manager.end_tick(agent_id)
manager.update_agent(agent_id, status="error")
manager.update_summary_memory(
agent_id,
f"ERROR: Failed to build system: {build_err}",
)
return
system = _make_lightweight_system(
server_engine, server_model, server_config,
)
executor.set_system(system)
executor.execute_tick(agent_id)
except Exception as exc:
logger.error(
"Run-tick failed for agent %s: %s",
agent_id, exc, exc_info=True,
)
try:
manager.end_tick(agent_id)
except Exception:
@@ -603,25 +623,56 @@ def create_agent_manager_router(
if not req.stream and req.mode == "immediate":
# Non-streaming immediate: trigger a background tick so the
# agent processes the message, then return the stored msg.
# Re-use the server's existing system (correct model/engine).
import threading
import time as _time
from openjarvis.agents.executor import AgentExecutor
from openjarvis.core.events import get_event_bus
from openjarvis.system import SystemBuilder
_srv_engine = getattr(request.app.state, "engine", None)
_srv_model = getattr(request.app.state, "model", "")
_srv_config = getattr(request.app.state, "config", None)
def _immediate_tick():
_start = _time.time()
logger.info(
"Immediate tick starting for agent %s "
"(model=%s)",
agent_id, _srv_model,
)
try:
executor = AgentExecutor(
manager=manager, event_bus=get_event_bus(),
)
try:
system = SystemBuilder().build()
executor.set_system(system)
except Exception:
return
system = _make_lightweight_system(
_srv_engine, _srv_model, _srv_config,
)
executor.set_system(system)
logger.info(
"Immediate tick: system ready in %.1fs, "
"executing tick for agent %s",
_time.time() - _start, agent_id,
)
executor.execute_tick(agent_id)
except Exception:
pass
logger.info(
"Immediate tick completed for agent %s "
"in %.1fs",
agent_id, _time.time() - _start,
)
except Exception as exc:
logger.error(
"Immediate tick failed for agent %s: %s",
agent_id, exc, exc_info=True,
)
try:
manager.end_tick(agent_id)
except Exception:
pass
manager.update_agent(agent_id, status="error")
manager.update_summary_memory(
agent_id, f"ERROR: {exc}",
)
threading.Thread(
target=_immediate_tick, daemon=True,