BoxAgentTask.kt
src/jvmMain/kotlin/afield/ai/agent/BoxAgentTask.kt · 94 lines
package afield.ai.agent
import afield.data.table.div
import afield.data.table.get
import afield.data.table.minus
import afield.data.table.plus
import afield.data.table.rem
import afield.data.table.times
import afield.document.graph.box
import afield.runtime.execution.plus
import afield.runtime.lifecycle.Runloop
import afield.runtime.lifecycle.plus
import afield.runtime.lifecycle.rem
import afield.ui.notation.with
import afield.ui.output.history
import afield.ui.output.transcript
import afield.ui.visualization.typed
import afield.audio.synth.*
import afield.document.graph.Box
import afield.graphics.vulkan.*
import afield.media.stream.*
import afield.media.video.*
import afield.runtime.lifecycle.map
import afield.ai.agent.AgentCheck
import afield.ai.agent.AgentTaskRequest
import afield.ai.agent.AgentTaskResult
import afield.ai.agent.AgentTaskRunner
import afield.ai.agent.FieldGraphTools
import afield.ai.agent.InFieldAgentEvals
import afield.ai.models.aiProviderStatus
import java.util.concurrent.CompletableFuture
fun Box.askFieldAgent
Hand goal to Field's own agent as an AI4 chat turn.
The turn runs in the AI4 panel of chatBox (default: this box). A live turn goes through the same
ai-assist handler as a message typed into that panel, so it is saved in the box's chat history,
the panel renders it, and you can open the box's AI4 chat and carry on the conversation. The
checks are evaluated against the document afterwards, and their verdict (PASS / FAIL / …) is
appended to the transcript as a progress block.
mode: live uses the configured AI provider; scripted replays script (a JSON list of tool
calls, see ScriptedLlmBridge) through the same tools with no model, and writes its tool calls into
the transcript labelled "scripted replay, no model was called"; auto is live when a provider key
is configured and scripted otherwise (it needs a script to fall back to).
chatBox is a Box or a box name/uid. checks use the AgentCheck.parse spelling —
last-run-ok:Primes, output-contains:Primes:29.
The task runs on a background thread, because the agent's tools wait for other boxes' verdicts and
those need the Runloop this box's code is running on. So this returns at once; the returned future
completes with the structured AgentTaskResult for code that wants it. Do not block on it from box
code. If observation expires while a live panel turn continues, its verdict is running and
chatBox identifies that continuing conversation. No completion checks or final verdict have
run; inspect the same chat instead of submitting the goal again.
fun Box.askFieldAgent(
goal: String,
checks: List<String> = emptyList(),
mode: String = "auto",
script: String = "",
maxToolRounds: Int = 16,
chatBox: Any? = null,
): CompletableFuture<AgentTaskResult> {
val info = inside
val target: Box = when (chatBox) {
null -> this
is Box -> chatBox
else -> FieldGraphTools(info).resolve(chatBox.toString())
}
val hasKey = runCatching { aiProviderStatus().let { it.hasOpenAiPlatformKey || it.hasOpenRouterApiKey || (it.provider == "local" && it.modelSettings.flagshipModel.isNotBlank()) } }.getOrDefault(false)
val resolved = when (mode) {
"auto" -> if (hasKey || script.isBlank()) "live" else "scripted"
else -> mode
}
val request = AgentTaskRequest(
goal = goal,
checks = checks.map(AgentCheck::parse),
mode = resolved,
script = if (resolved == "scripted") script else "",
maxToolRounds = maxToolRounds,
chatBox = target.id,
)
val future = CompletableFuture<AgentTaskResult>()
kotlin.concurrent.thread(name = "askFieldAgent-${target.id.take(8)}", isDaemon = true) {
future.complete(AgentTaskRunner.run(request, info))
}
return future
}
fun fieldAgentEvalScript(id: String): String = InFieldAgentEvals.byId(id).solution
The scripted solution of an eval task, for askFieldAgent's script — a no-credentials fallback.