When an AI agent product matures, the most expensive thing isn't features — it's the foundation. This article walks through the ground-up refactor Orkas did across its 1.0 release line — a full overhaul of model invocation, the agent loop, multi-agent orchestration, and the tool ecosystem — and the trade-offs behind each decision.
Why touch the foundation
Orkas is a local-first desktop AI agent workspace: all agent work runs inside a process on the user's own machine, data lives locally, and end-to-end cloud sync happens on demand. Features piled up fast in the early versions — a skills library, a knowledge base, connectors, group-chat-style multi-agent — but the further we went, the clearer it got: the real bottleneck wasn't any single feature, but three "foundation-level" things.
If the model-invocation layer follows the conversational old road, it gets shackled by a string of wrong assumptions. Calling a large model as if it were "one-question-one-answer chat" smuggles in a pile of defaults that make sense for chat but not for an agent: a fixed output-token cap, serial tool calls, hidden timeouts, a single hard-wired provider. An agent is a long-running flow that runs dozens of turns in a row, routinely brushes up against the context window, needs to read files in parallel, and can be interrupted by the user at any moment — every one of those defaults will bite you in production. Worse still, the most valuable capabilities of a desktop agent — fine-grained file operations, local search, running a shell, parallel multi-worker execution, long-horizon task solving — are exactly the ones that this layer of assumptions shuts out.
Orchestration was "static planning." The early version was a plan/DAG engine: first have the model break the task into a plan graph, then have an executor dispatch by the graph. It sounds tidy, but the reality of an agent is highly dynamic — reading one file reveals you need to change direction, and the result of one sub-task decides who the next step goes to. Freezing decisions into a pre-generated graph means every "the plan can't keep up with reality" has to be patched inside the executor.
The ecosystem was a closed catalog. Skills could only come from the official marketplace, connectors were a hard-coded catalog, and the external agent tools already on the user's machine were a complete black box to Orkas. A user wanting to plug in a third-party project, their own MCP server, or have an agent already on their machine call back into Orkas's skills and knowledge base — architecturally, none of it was possible.
The thesis of this refactor is simple: take the agent's foundation back into our own hands. Concretely, it lands as four interwoven through-lines — a self-built in-process runtime, a provider-agnostic model layer, dynamic group-chat orchestration, and the move from a closed catalog to an open host. Let's go through them one by one.
1. Bringing a full coding-agent capability set onto the desktop
The desktop is the agent's home turf — here there's a real file system, a real shell, a real local toolchain. An assistant that can only chat is wasting the environment; what actually leverages the desktop's advantage is a complete coding-agent capability set: file reads and writes down to the character range, cross-file search, running bash and system tools, spinning up multiple workers in parallel, and the long-horizon problem-solving to keep running dozens of turns until a complex task is genuinely finished.
The core deliverable of the refactor exists precisely to bring this capability set natively into Orkas's own process — a standalone, dynamically loadable, in-process agent runtime (called core-agent in the code). It isn't yet another chat wrapper; it's an agent engine Orkas controls itself.
The key architectural decision was to split it into two layers:
- Engine layer (standalone package): pure agent machinery — the tool-calling loop, streaming events, context compaction, error classification and retry, the provider abstraction, the sandbox, skill scanning, memory, self-evolution. It knows nothing about any Orkas business: it doesn't read business data directories, doesn't understand the conversation file format, and never touches IPC.
- Adapter layer (inside the main process): wires the engine into Orkas — session persistence, provider rotation, tool permissions, the skill registry, connectors, the knowledge base, the various generation tools. It translates the engine's native events into Orkas's own event shapes, so the business layer only ever sees a stable interface.
This engine/adapter boundary is the root of all the flexibility that follows. The engine can be tested and evolved independently; the adapter layer can safely absorb Orkas-specific complexity (rotation, cooldown, sandbox, permissions) without polluting the engine. The team's architecture review summed it up in one line: this is earned complexity — don't go merging it.
What the capability set actually is
Holding the runtime in our own hands isn't about showing off; it's about letting the agent genuinely "get its hands dirty" on the desktop. The capability set falls into roughly four groups:
- Fine-grained file operations and local search.
read_file supports reading by character range and auto-extracts text from PDF / Office documents; edit_file does precise "old string → new string" replacement and requires a read before any write; write_file lands the artifact and keeps an account of it; stat_file probes size; search_files locates by name/glob, grep_files searches content across files. This group lets the agent "dig through code, change files" in a real workspace like an engineer, instead of only being able to swallow and emit whole blocks. - Bash and system tools. A sandboxed shell executor, with a background-execution mode (long tasks detach from the current turn, logs go to a file) and risk-graded gating for dangerous operations. A large part of a desktop agent's leverage comes precisely from being able to directly command the system toolchain.
- Parallel multi-worker. Within a single turn, independent read-only tools run concurrently; at the task level, the commander can also fan out independent sub-tasks to multiple workers in parallel (see Section 3). Parallelizing where it's safe to is the key to compressing "long-horizon tasks" down to acceptable wall-clock time.
- Long-horizon reasoning and task solving. A loop that can run dozens of turns in a row, manage its own context, recover from errors, and never get stuck spinning in place — this is the dividing line between "finishing a complex job" and "answering a question."
How it was made production-grade in engineering terms
"Writing your own loop" sounds like asking for trouble, and it does carry maintenance cost. But what it buys is fine-grained control over the agent's entire lifecycle. That control isn't abstract — it's a set of concrete improvements, each one corresponding to whether one of the capabilities above "holds up" in production:
Real context window + compact only at 80%. The engine reads each model's real context window (including million-token-window models) and triggers compaction only when usage hits 80% — rather than conservatively starting at 60% and throwing away 40% of the useful context. There's also a "no-gain compaction" guardrail: if the retained tail already fills the window (say, a very large file-read result sitting in the tail), compaction can't free anything, so it just logs a warning and skips, never spinning a wasted summary call. Whether a long-horizon task can "remember what came before" all comes down to this.
Adjacent read-only tools in parallel. When the model fires off read-file, search-file, and web-lookup — several independent read-only tools — in one turn, the engine batches the adjacent parallelizable ones to run concurrently; write tools are natural barriers and keep their declared order. Tool calls and their results are committed strictly in declared order, so concurrency never breaks the protocol. The most common read-only tools go from serial to parallel in one stroke, and the whole batch's wall-clock time drops noticeably.
Read-before-write + optimistic concurrency control. Before editing a file you must read it first; the engine records a baseline for the file that was read and checks the baseline hasn't drifted at edit time. When parallel workers change the same file at once, the loser gets a clear "stale" error instead of silently overwriting each other's changes. With multiple workers operating on the same workspace in parallel, this safeguard is indispensable.
Mid-run interruption folded in immediately. When a user adds another line while the agent is halfway through, the engine folds that queued message into the current turn's input at the tool-loop boundary, instead of waiting to spin it up as a separate turn. This turns "course-correct while it runs" into a natural interaction.
Loop detection. When the same tool call repeats back-to-back, the engine first nudges (on the 3rd) and then hard-stops (on the 5th); any differing signature resets the count — legitimate variations like pagination/polling won't be misfired. When the model gets stuck, it no longer silently burns tokens.
Removing the hard cap on main-turn output. Main-turn output is no longer pinned to a tiny cap, so long reports and large edits don't get silently truncated; auxiliary calls (compaction, reflection) still conservatively use a small cap.
There's one very "local" detail worth a mention: token estimation for mixed Chinese-English text. A generic estimate under-counts a pure-Chinese conversation by two or three times; the engine treats Chinese and English characters differently by character class, which is what makes the compaction threshold trustworthy. That's the kind of thing a generic SDK won't think of for you.
Taken together, these improvements answer "why not just use an off-the-shelf SDK": because the strongest capabilities of a desktop agent happen to live in the very layer an SDK doesn't expose; to make them production-grade, the loop has to be in your own hands.
2. Keeping the model always online: the multi-layered provider wrapper
The goal of the model layer is one sentence: no matter what goes wrong with a given key, a given provider, or a given network, this turn of the user's conversation should survive if at all possible. To that end, the adapter layer stacks a few wrappers on top of the engine's provider abstraction — rotation, cooldown, registration, external adaptation.
The most critical design is that the rotator sits below the runner. The engine writes the user's message into the persistent session before it ever calls a provider; if you did retry/rotation at the engine level, you'd either resubmit the user message or have to write a whole session-rollback. By placing the rotator beneath the engine, the user message is written exactly once, and "retry with another candidate" is completely transparent to session state.
The rotator's judgment is also restrained, centered on the line of the first content event:
- A failure before the model emits any substantive content (text/tool call) — safe to switch to the next candidate;
- Once the first content event is emitted — stop rotating and let the error propagate up, because the model may already have run a full turn, and redoing it would repeat side effects.
Error classification decides "rotate, don't rotate, or retry." Account-level failures like auth failure, insufficient balance, rate limiting, expired subscription — mark a cooldown and rotate; transient network failures like a connection reset — no cooldown, retry statelessly a few times in place; while malformed requests, content policy, and server 5xx — which would fail just the same with another key — pass straight through without rotating. The cooldown is a ten-minute, in-process, non-persistent hint: it's only a short-term signal, not worth writing to disk on every failure, and a process restart is exactly the right moment to re-probe.
On the provider "roster" side, the refactor flattens three kinds of sources into one unified abstraction:
- Orkas-managed LLM: a server-side proxy, ready to use once signed in, with the server routing between text/image models;
- Bring-your-own key: standard mainstream large-model providers;
- External direct-connect adapters: a batch of models that need direct connection or carry their own billing, hand-adapted onto the same provider interface.
To the layers above, all of this appears as just one stable (provider, model) pair — rotation, cooldown, and external adaptation are all hidden inside the adapter layer.
3. Group chat as orchestration: from static plan-DAG to commander-in-the-loop
This is the most "rewire-your-brain" part of the refactor.
The old model was static planning: the model first generates a plan/DAG and the executor runs by the graph. The new model tears that graph out entirely and replaces it with a dynamic, commander-in-the-loop group-chat orchestration.
Its metaphor is a group-chat room:
- The Commander is the room's host, not an invisible middleware;
- Agent workers are first-class, equal members in the room;
- All interactions are asynchronous messages, enqueued through one single message bus (there is no private path for parallel fan-out).
The commander's "dispatch" is not an @somebody written in prose — an LLM writing @AgentA in body text is just markdown from training data, and untrustworthy. The real dispatch signal is a structured tool call, and after the refactor it converges into three semantically clear actions:
dispatch_to — send an agent to run to completion and hand the result back, with the commander synthesizing. Multiple independent tasks can fan out concurrently.run_worker — a sub-task the commander owns itself, with the result returned synchronously; an anonymous worker is the commander's "hand" (invisible to the user), while a named worker is a visible specialist.hand_off_to — hand the conversation to the agent; the commander steps out, and the agent answers the user directly with no synthesis on top this turn.
Why group chat, rather than an orchestrator or a sub-agent tree
Making multi-agent into group chat brings several benefits a traditional orchestrator / sub-agent tree can't get:
- Visibility slices. Each message is only appended to the slice of "those who can see it." When an agent worker starts up it replays only its own slice, so another agent's large output won't pollute its context. The commander sees everything.
- Minimal state. The whole orchestration's core state is just "who currently holds the floor" plus a lightweight task ledger. No DAG, no complex state machine.
- Naturally replayable and syncable. Messages sort naturally by timestamp, so reloading and cross-device sync both fall directly onto the message stream. The mobile end does remote control precisely off this stream — all agent compute runs on the desktop, and mobile is just a mirror render, needing no special orchestration protocol.
The team's architecture review was equally blunt on this point: a group-chat bus plus a commander-in-the-loop is Orkas's multi-agent shape; stacking another parallel sub-agent dispatch path inside the process would instead violate the invariant of "only one group-chat dispatch path."
What's new in this version: interactive hand-off
The newest piece on this line is the interactive agent hand-off.
The pain point is concrete: a "tutor"-type agent teaches the user for a turn, the user wants to keep asking follow-ups, but the system forces the floor back to the commander, leaving the user to re-@ that agent for every single line.
The solution is a server-authoritative floor + a model-decided recipient:
- The floor becomes a persistent state field, saved across reloads, and rides the existing state-change event to auto-sync to every end — no new event type needed.
- After the commander uses
hand_off_to to give the floor to an interactive agent, the user's subsequent "no-@" messages go straight to that agent, until the agent hands back on its own or the user re-addresses the commander. - The agent hands control back with a
<handback /> marker; parsing strictly verifies a true match (so a stray <handback appearing in prose isn't misread as a hand-off). - If there's an unfinished task ledger at hand-back time, the commander picks it up from the ledger and carries on.
There's also an experience fix that comes with it — commander loop bubbles. The commander's "dispatch → read result → dispatch again" loop within one turn used to be flattened into a single bubble, and on reload it would even jump out of order to the bottom. The refactor cuts one turn into multiple segments at each visible-dispatch boundary, each segment a standalone message with an ascending timestamp — for the first time the user can see the commander "looping through orchestration," and the reload order is correct too.
Finally, two safety nets that run throughout: group abort is the single stop path for all actors (the moment the user hits Stop, every worker's abort signal is cut, with even anonymous sub-workers covered by a fallback match); and the previously mentioned interrupt-steer, folding the user's mid-run interjection into the current turn.
4. From a closed catalog to an open host
If the first three through-lines were about making the foundation solid, this one is about throwing all the doors and windows open — turning Orkas from a closed catalog into an open host — while holding the security boundary without giving an inch.
The refactor systematically dismantled several "closed" choke points:
External packages. The user gives a repository address, and Orkas hosts it locally cloned verbatim into a folder — never normalized, never rewritten, never synced to the cloud (because it contains third-party dependency directories). A standalone command-line tool owns the install/update/start-stop lifecycle, scans whether it's "skill-shaped" (carries a skill-description file) or "CLI-shaped" (carries an executable entry), and writes the metadata into a registry outside the package directory (so future pull-updates never conflict). Dependency installation goes through "ask once, remember" two-step confirmation; executable entries get shims generated and injected into the bash tool's PATH, so the model can call these third-party CLIs directly.
Multi-root skill loading. The single entry point for skill execution went from recognizing only two roots to four tiers — custom / marketplace / external package / global — resolved by priority; scripts inside an external package prefer the package's own bundled dependency environment. This is the choke point with the highest regression risk, and it's backed by a full fixture matrix.
Global skill interop. Orkas reads directly from the global skill directories that other agent tools on the user's machine already maintain, achieving interop at the skill level — a skill the user accumulated in one place is usable in Orkas too. The user placing a skill into those directories is itself the authorization, so it's enabled by default, with a master switch left in. These third-party skill descriptions are an untrusted prompt-injection surface, so they go through the "open-tier" loader, are visible only to the commander, and structurally cannot enter an agent's skill allowlist.
User-configured MCP. Connectors are no longer a hard-coded catalog. The user can add any MCP server — a remote HTTP form (low risk) or a local subprocess form (high risk). The form itself is the consent surface (the command the user typed in by hand is shown verbatim), the transport config (including secrets) goes entirely into encrypted storage, and custom instances always carry a fixed prefix so they can never impersonate an official connector in the catalog.
Reverse bridge: letting the machine's external agents perceive Orkas in turn. This is the most interesting piece. External agent tools already on the user's machine used to be a black box to Orkas; now, when Orkas dispatches them, it injects a bridge channel that lets them in reverse list/read/run Orkas's skills, call connectors, and search the knowledge base. The bridge runs over a local inter-process channel (no network port opened), authenticated with a one-time credential that is unique per run and destroyed the moment the run ends. Every connector call with an external side effect goes through a user confirmation dialog — not a heuristic read/write judgment by tool name (which would err on the side of being too loose), but one confirmation per (agent, connector), with an optional "always allow."
Long-tail coding posture. The commander's decision tree gains a branch: when there's no matching agent/skill/connector, assess solving it directly with bash plus a short script, do it this turn, verify the output, and optionally offer to crystallize it into a custom skill. This comes with background bash execution (long tasks detach from the current turn, logs go to a file) and user-granted directories.
Open, but not hands-off
What you fear most when opening the doors and windows is a draft. The discipline of this refactor: not a single one of the spawn choke points for "dangerous actions" is touched. MCP starts from exactly one place, skill execution goes through exactly one runner, and bash goes through exactly one sandbox executor. On top of that, several layers of depth are stacked:
- File operations always pass through the path sandbox (workspace + current attachments + directories the user explicitly granted), while credential directories, system directories, and Orkas's own directories cannot be granted;
- Dangerous bash (exfiltration, destructive deletion, privilege escalation, sensitive paths) triggers a permission confirmation, with the decision split into "just this once / for this run / deny," and logs recording only category and length — never the command text;
- External-package installation fail-closes and outright refuses packages with symlink members (to prevent using a symlink to read sensitive files outside the sandbox into scope), and the clone source is restricted to a protocol allowlist;
- All credential-bearing transport/secrets are encrypted at rest, and bridge credentials are isolated per run;
- The open-source / hosted distribution strips host-exclusive capabilities by a trimming rule.
In one sentence: every explicit user action (install / grant / submit form / click confirm) is the credential of consent, and every consent is confined to the boundary it deserves.
5. Getting smarter across sessions: memory and self-evolution
The foundation overhaul also redid two subsystems that "make the agent smarter the more it's used," both following the same engineering discipline — off by default, bounded, observable.
Cross-session memory uses hybrid retrieval: vector semantic search + keyword search (BM25), merged via RRF (reciprocal rank fusion) to avoid either single channel failing; it lands in local storage (with a full-text index). Memory comes in two kinds — the agent's own notes and the user-preference profile — each with a character cap, scanned for injection threats before writing, and frozen-injected into the system prompt at the start of every turn. The whole memory system serves only to make the agent understand the current user better; the data always stays local, and the user can view, edit, and export it anytime in settings.
Self-evolution is an agent-private skill library (stored separately from the platform's shared skill library) plus a layer of metacognitive reflection. The engine decides whether to reflect by a set of weighted signals: user correction (highest weight), recovery from a non-trivial error, task complexity, a known weakness being triggered or overcome, skill ineffectiveness... reflection only fires when the weighted signals exceed a threshold. The reflection itself is a background periodic task (roughly one round every 12 hours, a several-hour cooldown, a multi-day fallback) that uses a cheap small model to read a summary of recent activity and decide whether to create/patch a skill and update the agent's "competence profile."
The most important safety point: self-evolution is enabled only for sessions that have an agent explicitly bound — the default commander session does not evolve. Reflection has a dual token cap (count + total), one agent's failure doesn't block the others, and per-run cost is pushed extremely low. Make the agent smarter, but don't let it run away.
Engineering philosophy: earned complexity — don't simplify it away
The team ran multiple rounds of architecture review during the refactor, and one conclusion kept recurring, worth pulling out on its own: distinguish "organizational hypertrophy" from "earned complexity," and only touch the former.
- The sync engine's multiple merge strategies, the self-built agent loop, the multi-layered provider wrapper, the boundary of mobile remote control — these look complex, but every layer earns its keep (multi-device eventual consistency, deep integration, multi-key rotation, a product-decided end boundary). Forcibly "simplifying" them would only lose data and muddy the layering.
- What truly should be touched are the "god modules" and local duplication: extracting the stateless pure functions out of the bloated group-chat bus (prompt assembly, commander tools, the CLI turn), and collapsing the "confirmation dialog" pattern that was duplicated several times into one shared component.
What backs this kind of judgment is a set of hard disciplines written into the project's constraints document: boundary (single process, IPC as the only path, the runtime can only be dynamically loaded), layering (the dependency direction of each layer), single source of truth (categories, telemetry taxonomy, domains), and a mandatory "prompt audit" on every prompt-facing commit. What lets the foundation be overhauled without collapsing isn't some clever design — it's these invariants being held continuously.
Closing
Put the four through-lines together, and what this ground-up refactor swaps onto Orkas is an agent foundation that is self-controlled, provider-agnostic, dynamically orchestrated, open to the outside, and able to self-evolve:
- A two-layer engine/adapter in-process runtime that brings a full set of coding-agent strengths — file operations, local search, system tools, parallel multi-worker, long-horizon solving — natively onto the desktop and makes each one production-grade;
- A multi-layered model layer that keeps the conversation alive through key/provider/network turbulence as much as possible;
- A group-chat-style, commander-in-the-loop multi-agent orchestration that swaps "static planning" for "dynamic decision," and for the first time makes hand-offs between agents feel natural;
- An ecosystem moving from a closed catalog to an open host, with external packages, global skills, custom MCP, and the reverse bridge all opened up — while the spawn choke points didn't budge an inch;
- And memory and self-evolution that are off by default, bounded, and observable.
Features can be added one at a time, but a foundation is only worth seriously overhauling once. Once it's done, whatever you build on top goes faster — and that's exactly the outcome this refactor was after.
Quando um produto de agente de IA amadurece, o mais caro não são os recursos, mas a base. Este artigo aborda a refatoração básica que Orkas fez em sua linha de lançamento 1.0 — uma revisão completa da invocação de modelo, do loop de agente, da orquestração multiagente e do ecossistema de ferramentas — e as compensações por trás de cada decisão.
Por que tocar na base
Orkas é um espaço de trabalho local para agentes de IA de desktop: todo o trabalho do agente é executado dentro de um processo na própria máquina do usuário, os dados residem localmente e a sincronização de ponta a ponta na nuvem acontece sob demanda. Os recursos se acumularam rapidamente nas primeiras versões — uma biblioteca de habilidades, uma base de conhecimento, conectores, multiagente estilo chat em grupo — mas quanto mais avançávamos, mais claro ficava: o verdadeiro gargalo não era um recurso único, mas três coisas de "nível básico".
Se a camada de invocação de modelo seguir o velho caminho conversacional, ela ficará presa a uma série de suposições erradas. Chamar um modelo grande como se fosse um "bate-papo de uma pergunta e uma resposta" contrabandeia uma pilha de padrões que fazem sentido para o bate-papo, mas não para um agente: um limite fixo de token de saída, chamadas de ferramentas seriais, tempos limite ocultos, um único provedor conectado. Um agente é um fluxo de longa execução que executa dezenas de turnos seguidos, esbarra rotineiramente na janela de contexto, precisa ler arquivos em paralelo e pode ser interrompido pelo usuário a qualquer momento — cada um desses padrões irá incomodá-lo na produção. Pior ainda, os recursos mais valiosos de um agente de desktop – operações refinadas de arquivos, pesquisa local, execução de um shell, execução paralela de vários trabalhadores, resolução de tarefas de longo horizonte – são exatamente aqueles que essa camada de suposições exclui.
A orquestração era um "planejamento estático". A versão inicial era um mecanismo de plano/DAG: primeiro, o modelo dividia a tarefa em um gráfico de plano e, em seguida, enviava um executor pelo gráfico. Parece simples, mas a realidade de um agente é altamente dinâmica: a leitura de um arquivo revela que você precisa mudar de direção, e o resultado de uma subtarefa decide para quem vai o próximo passo. Congelar decisões em um gráfico pré-gerado significa que cada "plano não consegue acompanhar a realidade" precisa ser corrigido dentro do executor.
O ecossistema era um catálogo fechado. As habilidades só podiam vir do mercado oficial, os conectores eram um catálogo codificado e as ferramentas de agente externo já na máquina do usuário eram uma caixa preta completa para Orkas. Um usuário que deseja conectar um projeto de terceiros, seu próprio servidor MCP ou ter um agente já em sua máquina liga de volta para as habilidades e base de conhecimento de Orkas. Arquitetonicamente, nada disso era possível.
A tese deste refatorador é simples: tomar a base do agente de volta em nossas próprias mãos. Concretamente, ele resulta em quatro linhas interligadas: um tempo de execução no processo autoconstruído, uma camada de modelo independente de provedor, orquestração dinâmica de bate-papo em grupo e a mudança de um catálogo fechado para um host aberto. Vamos analisá-los um por um.
1. Trazendo um conjunto completo de recursos de agente de codificação para o desktop
A área de trabalho é a casa do agente — aqui há um sistema de arquivos real, um shell real, um conjunto de ferramentas local real. Um assistente que só consegue conversar está desperdiçando o meio ambiente; o que realmente aproveita a vantagem do desktop é um conjunto completo de recursos de agente de codificação: leitura e gravação de arquivos no intervalo de caracteres, pesquisa entre arquivos, execução de bash e ferramentas de sistema, ativação de vários trabalhadores em paralelo e solução de problemas de longo horizonte para continuar executando dezenas de turnos até que uma tarefa complexa seja realmente concluída.
O principal produto do refatorador existe precisamente para trazer esse conjunto de recursos nativamente para o próprio processo do Orkas — um tempo de execução de agente em processo independente e carregável dinamicamente (chamado core-agent no código). Não é mais um wrapper de bate-papo; é um mecanismo de agente que Orkas controla.
A principal decisão arquitetônica foi dividi-la em duas camadas:
- Camada de mecanismo (pacote independente): maquinário de agente puro — o loop de chamada de ferramentas, eventos de streaming, compactação de contexto, classificação de erros e novas tentativas, abstração do provedor, sandbox, verificação de habilidades, memória, autoevolução. Ele não sabe nada sobre nenhum negócio Orkas: ele não lê diretórios de dados comerciais, não entende o formato do arquivo de conversação e nunca toca no IPC.
- Camada adaptadora (dentro do processo principal): conecta o mecanismo ao Orkas — persistência de sessão, rotação de provedores, permissões de ferramentas, registro de habilidades, conectores, base de conhecimento, as diversas ferramentas de geração. Ele traduz os eventos nativos do mecanismo nos formatos de eventos do próprio Orkas, de forma que a camada de negócios veja apenas uma interface estável.
Esse limite entre motor/adaptador é a raiz de toda a flexibilidade que se segue. O motor pode ser testado e evoluído de forma independente; a camada do adaptador pode absorver com segurança a complexidade específica do Orkas (rotação, resfriamento, sandbox, permissões) sem poluir o mecanismo. A revisão da arquitetura da equipe resumiu tudo em uma linha: isso é complexidade adquirida — não vá mesclá-la.
Qual é realmente o conjunto de recursos
Segurar o tempo de execução com as próprias mãos não significa se exibir; trata-se de deixar o agente genuinamente “sujar as mãos” na área de trabalho. O conjunto de recursos se divide em aproximadamente quatro grupos:
- Operações de arquivo refinadas e pesquisa local.
read_file suporta leitura por intervalo de caracteres e extrai automaticamente texto de documentos PDF/Office; edit_file faz uma substituição precisa de "string antiga → nova string" e requer uma leitura antes de qualquer gravação; write_file obtém o artefato e mantém um registro dele; stat_file tamanho do teste; search_files localiza por nome/glob, grep_files pesquisa conteúdo em arquivos. Este grupo permite que o agente "explore o código, altere arquivos" em um espaço de trabalho real como um engenheiro, em vez de apenas engolir e emitir blocos inteiros. - Bash e ferramentas de sistema. Um executor de shell em sandbox, com um modo de execução em segundo plano (tarefas longas são separadas do turno atual, os logs vão para um arquivo) e portas com classificação de risco para operações perigosas. Uma grande parte da vantagem de um agente de desktop vem precisamente da capacidade de comandar diretamente a cadeia de ferramentas do sistema.
- Multi-trabalhador paralelo. Em um único turno, ferramentas independentes somente leitura são executadas simultaneamente; no nível da tarefa, o comandante também pode distribuir subtarefas independentes para vários trabalhadores em paralelo (ver Seção 3). Paralelizar onde é seguro é a chave para compactar "tarefas de longo horizonte" em um tempo aceitável de relógio.
- Raciocínio de longo horizonte e resolução de tarefas. Um loop que pode executar dezenas de voltas seguidas, gerenciar seu próprio contexto, se recuperar de erros e nunca ficar preso no lugar — esta é a linha divisória entre "terminar um trabalho complexo" e "responder a uma pergunta".
Como foi feito o nível de produção em termos de engenharia
"Escrever seu próprio loop" soa como pedir encrenca e acarreta custos de manutenção. Mas o que ele compra é um controle refinado sobre todo o ciclo de vida do agente. Esse controle não é abstrato — é um conjunto de melhorias concretas, cada uma correspondendo ao fato de um dos recursos acima "se manter" na produção:
Janela de contexto real + compactação apenas em 80%. O mecanismo lê a janela de contexto real de cada modelo (incluindo modelos de janela de um milhão de tokens) e aciona a compactação somente quando o uso atinge 80% — em vez de começar conservadoramente em 60% e descartar 40% do contexto útil. Há também uma proteção de "compactação sem ganho": se a cauda retida já preencher a janela (digamos, um resultado de leitura de arquivo muito grande na cauda), a compactação não pode liberar nada, então apenas registra um aviso e pula, nunca girando uma chamada de resumo desperdiçada. Se uma tarefa de longo horizonte pode "lembrar o que veio antes", tudo se resume a isso.
Ferramentas somente leitura adjacentes em paralelo. Quando o modelo dispara arquivo de leitura, arquivo de pesquisa e pesquisa na web — diversas ferramentas somente leitura independentes — de uma só vez, o mecanismo agrupa em lote as ferramentas paralelizáveis adjacentes para execução simultânea; as ferramentas de gravação são barreiras naturais e mantêm sua ordem declarada. As chamadas de ferramentas e seus resultados são confirmados estritamente na ordem declarada, para que a simultaneidade nunca quebre o protocolo. As ferramentas somente leitura mais comuns passam de serial para paralelo de uma só vez, e o tempo do relógio de parede de todo o lote cai visivelmente.
Leitura antes de gravação + controle de simultaneidade otimista. Antes de editar um arquivo você deve lê-lo primeiro; o mecanismo registra uma linha de base para o arquivo que foi lido e verifica se a linha de base não mudou no momento da edição. Quando trabalhadores paralelos alteram o mesmo arquivo de uma só vez, o perdedor recebe um claro erro "obsoleto" em vez de sobrescrever silenciosamente as alterações uns dos outros. Com vários trabalhadores operando no mesmo espaço de trabalho em paralelo, essa proteção é indispensável.
A interrupção no meio da execução é implementada imediatamente. Quando um usuário adiciona outra linha enquanto o agente está na metade, o mecanismo dobra a mensagem na fila na entrada do turno atual no limite do loop de ferramentas, em vez de esperar para transformá-la em um turno separado. Isso transforma o "curso correto enquanto ele é executado" em uma interação natural.
Detecção de loop. Quando a mesma chamada de ferramenta se repete consecutivamente, o motor primeiro dá um empurrão (no 3º) e depois para bruscamente (no 5º); qualquer assinatura diferente redefine a contagem - variações legítimas como paginação/sondagem não falharão. Quando o modelo trava, ele não queima mais tokens silenciosamente.
Remoção do limite máximo na saída da curva principal. A saída da curva principal não fica mais fixada em uma tampa pequena, para que relatórios longos e edições grandes não sejam truncados silenciosamente; chamadas auxiliares (compactação, reflexão) ainda usam versaletes de maneira conservadora.
Há um detalhe muito "local" que vale a pena mencionar: estimativa de token para texto misto chinês-inglês. Uma estimativa genérica subestima uma conversa em chinês puro em duas ou três vezes; o mecanismo trata os caracteres chineses e ingleses de maneira diferente por classe de caracteres, o que torna o limite de compactação confiável. Esse é o tipo de coisa que um SDK genérico não pensará para você.
Juntas, essas melhorias respondem "por que não usar apenas um SDK pronto para uso": porque os recursos mais fortes de um agente de desktop residem na mesma camada que um SDK não expõe; para torná-los de nível de produção, o circuito deve estar em suas próprias mãos.
2. Mantendo o modelo sempre online: o wrapper do provedor multicamadas
O objetivo da camada de modelo é uma frase: não importa o que dê errado com uma determinada chave, um determinado provedor ou uma determinada rede, essa mudança na conversa do usuário deve sobreviver, se possível. Para esse fim, a camada do adaptador empilha alguns wrappers sobre a abstração do provedor do mecanismo — rotação, resfriamento, registro, adaptação externa.
O projeto mais crítico é que o rotor fique abaixo do corredor. O mecanismo grava a mensagem do usuário na sessão persistente antes mesmo de chamar um provedor; se você tentasse novamente/rotação no nível do mecanismo, reenviaria a mensagem do usuário ou teria que escrever uma reversão de sessão inteira. Ao colocar o rotador abaixo do mecanismo, a mensagem do usuário é escrita exatamente uma vez e "tentar novamente com outro candidato" é completamente transparente para o estado da sessão.
O julgamento do rotador também é restrito, centrado na linha do primeiro evento de conteúdo:
- Uma falha antes do modelo emitir qualquer conteúdo substantivo (texto/chamada de ferramenta) — é seguro mudar para o próximo candidato;
- Depois que o primeiro evento de conteúdo for emitido, pare de girar e deixe o erro se propagar, porque o modelo pode já ter rodado uma volta completa e refazê-lo repetiria os efeitos colaterais.
A classificação do erro decide "girar, não girar ou tentar novamente". Falhas no nível da conta, como falha de autenticação, saldo insuficiente, limitação de taxa, assinatura expirada — marque um período de espera e alterne; Falhas de rede transitórias, como redefinição de conexão — sem resfriamento, tente novamente sem estado algumas vezes; enquanto solicitações malformadas, política de conteúdo e servidor 5xx – que falhariam da mesma forma com outra chave – passam direto sem rotação. O tempo de espera é uma dica de dez minutos, em processo, não persistente: é apenas um sinal de curto prazo, não vale a pena gravar no disco em cada falha, e uma reinicialização do processo é exatamente o momento certo para testar novamente.
No lado da "lista" do provedor, o refatorador nivela três tipos de fontes em uma abstração unificada:
- LLM gerenciado pelo Orkas: um proxy do lado do servidor, pronto para uso uma vez conectado, com o roteamento do servidor entre modelos de texto/imagem;
- Traga sua própria chave: provedores convencionais padrão de grandes modelos;
- Adaptadores externos de conexão direta: um lote de modelos que precisam de conexão direta ou carregam seu próprio faturamento, adaptados manualmente na mesma interface do provedor.
Para as camadas acima, tudo isso aparece como apenas um par (provedor, modelo) estável — rotação, resfriamento e adaptação externa estão todos ocultos dentro da camada do adaptador.
3. Bate-papo em grupo como orquestração: do plano estático-DAG ao comandante no circuito
Esta é a parte mais “reestruturante” do refatorador.
O modelo antigo era o planejamento estático: o modelo primeiro gera um plano/DAG e o executor executa pelo gráfico. O novo modelo elimina totalmente esse gráfico e o substitui por uma orquestração de bate-papo em grupo dinâmica e controlada.
Sua metáfora é uma sala de bate-papo em grupo:
- O Comandante é o anfitrião da sala, não um middleware invisível;
- Trabalhadores agentes são membros iguais e de primeira classe na sala;
- Todas as interações são mensagens assíncronas, enfileiradas por meio de um único barramento de mensagens (não há caminho privado para distribuição paralela).
O "despacho" do comandante não é um @alguém escrito em prosa - um LLM escrito @AgentA no corpo do texto é apenas uma redução dos dados de treinamento e não é confiável. O verdadeiro sinal de despacho é uma chamada de ferramenta estruturada e, após a refatoração, converge em três ações semanticamente claras:
dispatch_to — envia um agente para concluir a execução e devolve o resultado, com o comandante sintetizando. Várias tarefas independentes podem ser distribuídas simultaneamente.run_worker — uma subtarefa de propriedade do próprio comandante, com o resultado retornado de forma síncrona; um trabalhador anônimo é a "mão" do comandante (invisível para o usuário), enquanto um trabalhador nomeado é um especialista visível.hand_off_to — entrega a conversa para o agente; o comandante sai e o agente responde diretamente ao usuário, sem nenhuma síntese neste turno.
Por que conversar em grupo, em vez de um orquestrador ou uma árvore de subagentes
Transformar multiagentes em bate-papo em grupo traz vários benefícios que uma árvore orquestradora/subagente tradicional não consegue obter:
- Fatias de visibilidade. Cada mensagem é anexada apenas à fatia "aqueles que podem vê-la". Quando um agente de trabalho é iniciado, ele reproduz apenas sua própria fatia, portanto, a grande saída de outro agente não poluirá seu contexto. O comandante vê tudo.
- Estado mínimo. O estado central de toda a orquestração é apenas "quem atualmente detém a palavra" mais um livro de tarefas leve. Sem DAG, sem máquina de estado complexa.
- Naturalmente reproduzíveis e sincronizáveis. As mensagens são classificadas naturalmente por carimbo de data e hora, de modo que o recarregamento e a sincronização entre dispositivos caem diretamente no fluxo de mensagens. A extremidade móvel faz o controle remoto precisamente desse fluxo: toda a computação do agente é executada no desktop, e a mobilidade é apenas uma renderização espelhada, sem necessidade de protocolo de orquestração especial.
A revisão da arquitetura da equipe foi igualmente direta neste ponto: um barramento de bate-papo em grupo mais um comandante no circuito é o formato multiagente de Orkas; empilhar outro caminho de despacho de subagente paralelo dentro do processo violaria a invariante de "apenas um caminho de despacho de bate-papo em grupo".
O que há de novo nesta versão: transferência interativa
A mais nova peça desta linha é a transferência interativa de agentes.
O ponto problemático é concreto: um agente do tipo "tutor" ensina o usuário por um turno, o usuário quer continuar pedindo acompanhamentos, mas o sistema força a palavra de volta ao comandante, deixando o usuário re-@ aquele agente para cada linha.
A solução é um piso autoritativo do servidor + um destinatário decidido pelo modelo:
- O piso se torna um campo de estado persistente, salvo durante as recargas, e utiliza o evento de mudança de estado existente para sincronização automática em todas as extremidades, sem necessidade de novo tipo de evento.
- Depois que o comandante usa
hand_off_to para dar a palavra a um agente interativo, as mensagens "no-@" subsequentes do usuário vão diretamente para esse agente, até que o agente devolva por conta própria ou o usuário volte a se dirigir ao comandante. - O agente devolve o controle com um marcador
<handback />; a análise verifica estritamente uma correspondência verdadeira (portanto, um <handback perdido que aparece em prosa não é mal interpretado como uma transferência). - Se houver um livro de tarefas inacabado no momento da devolução, o comandante o pega no livro e continua.
Há também uma correção de experiência que vem com ele: bolhas de loop de comandante. O loop "despacho → ler resultado → despachar novamente" do comandante dentro de um turno costumava ser achatado em uma única bolha e, ao recarregar, ele até saltava fora de ordem para o fundo. O refatorador corta uma volta em múltiplos segmentos em cada limite de despacho visível, cada segmento uma mensagem independente com um carimbo de data e hora ascendente – pela primeira vez o usuário pode ver o comandante "fazendo um loop pela orquestração" e a ordem de recarga também está correta.
Finalmente, duas redes de segurança estão presentes: o aborto em grupo é o único caminho de parada para todos os atores (no momento em que o usuário clica em Parar, o sinal de aborto de cada trabalhador é cortado, até mesmo subtrabalhadores anônimos são cobertos por uma correspondência de fallback); e o mencionado anteriormente interrupt-steer, dobrando a interjeição do usuário no meio da corrida para a curva atual.
4. De um catálogo fechado a um host aberto
Se as três primeiras linhas foram sobre tornar a base sólida, esta é sobre abrir todas as portas e janelas - transformar Orkas de um catálogo fechado em um host aberto - enquanto mantém os limites de segurança sem ceder um centímetro.
O refatorador desmontou sistematicamente vários pontos de estrangulamento "fechados":
Pacotes externos. O usuário fornece um endereço de repositório e o Orkas o hospeda localmente clonado literalmente em uma pasta — nunca normalizado, nunca reescrito, nunca sincronizado com a nuvem (porque contém diretórios de dependência de terceiros). Uma ferramenta de linha de comando independente possui o ciclo de vida de instalação/atualização/start-stop, verifica se é "em forma de habilidade" (carrega um arquivo de descrição de habilidade) ou "em forma de CLI" (carrega uma entrada executável) e grava os metadados em um registro fora do diretório do pacote (para que futuras atualizações pull nunca entrem em conflito). A instalação da dependência passa pela confirmação em duas etapas do tipo "pergunte uma vez, lembre-se"; entradas executáveis obtêm correções geradas e injetadas no PATH da ferramenta bash, para que o modelo possa chamar essas CLIs de terceiros diretamente.
Carregamento de habilidades multi-raiz. O único ponto de entrada para a execução de habilidades passou do reconhecimento de apenas duas raízes para quatro níveis — personalizado/mercado/pacote externo/global — resolvidos por prioridade; scripts dentro de um pacote externo preferem o ambiente de dependência agrupado do próprio pacote. Este é o ponto de estrangulamento com o maior risco de regressão e é apoiado por uma matriz de fixtures completa.
Interoperabilidade global de habilidades. O Orkas lê diretamente dos diretórios de habilidades globais que outras ferramentas de agente na máquina do usuário já mantêm, alcançando a interoperabilidade no nível da habilidade — uma habilidade que o usuário acumulou em um local também pode ser usada no Orkas. O próprio usuário que coloca uma habilidade nesses diretórios é a autorização, portanto ela é habilitada por padrão, com um interruptor mestre deixado. Essas descrições de habilidades de terceiros são uma superfície de injeção de prompt não confiável, portanto, elas passam pelo carregador de "camada aberta", são visíveis apenas para o comandante e estruturalmente não podem entrar na lista de permissões de habilidades de um agente.
MCP configurado pelo usuário. Os conectores não são mais um catálogo codificado. O usuário pode adicionar qualquer servidor MCP — um formulário HTTP remoto (baixo risco) ou um formulário de subprocesso local (alto risco). O formulário em si é a superfície de consentimento (o comando que o usuário digitou manualmente é mostrado literalmente), a configuração de transporte (incluindo segredos) vai inteiramente para o armazenamento criptografado e as instâncias personalizadas sempre carregam um prefixo fixo para que nunca possam representar um conector oficial no catálogo.
Ponte reversa: permitindo que os agentes externos da máquina percebam Orkas por sua vez. Esta é a peça mais interessante. As ferramentas de agentes externos já instaladas na máquina do usuário costumavam ser uma caixa preta para Orkas; agora, quando Orkas os despacha, ele injeta um canal de ponte que permite ao contrário listar/ler/executar as habilidades de Orkas, chamar conectores e pesquisar a base de conhecimento. A ponte é executada em um canal local entre processos (nenhuma porta de rede aberta), autenticada com uma credencial única que é exclusiva por execução e destruída no momento em que a execução termina. Toda chamada de conector com um efeito colateral externo passa por uma caixa de diálogo de confirmação do usuário — não um julgamento heurístico de leitura/gravação por nome de ferramenta (que seria um erro por ser muito vago), mas uma confirmação por (agente, conector), com um opcional "sempre permitir".
Postura de codificação de cauda longa. A árvore de decisão do comandante ganha uma ramificação: quando não há agente/habilidade/conector correspondente, avalie a solução diretamente com bash mais um script curto, faça isso neste turno, verifique a saída e, opcionalmente, ofereça-se para cristalizá-la em uma habilidade personalizada. Isso vem com a execução do bash em segundo plano (tarefas longas são separadas do turno atual, os logs vão para um arquivo) e diretórios concedidos pelo usuário.
Aberto, mas sem intervenção
O que você mais teme ao abrir as portas e janelas é uma corrente de ar. A disciplina deste refatorador: nenhum dos pontos de estrangulamento de spawn para "ações perigosas" é tocado. O MCP começa exatamente em um lugar, a execução da habilidade passa por exatamente um runner e o bash passa por exatamente um executor sandbox. Além disso, várias camadas de profundidade são empilhadas:
- As operações de arquivo sempre passam pelo caminho sandbox (espaço de trabalho + anexos atuais + diretórios que o usuário concedeu explicitamente), enquanto diretórios de credenciais, diretórios de sistema e os próprios diretórios do Orkas não podem ser concedidos;
- Bash perigoso (exfiltração, exclusão destrutiva, escalonamento de privilégios, caminhos confidenciais) aciona uma confirmação de permissão, com a decisão dividida em "só desta vez/para esta execução/negar" e os registros registram apenas categoria e comprimento — nunca o texto do comando;
- A instalação de pacotes externos fecha com falha e recusa completamente pacotes com membros de link simbólico (para evitar o uso de um link simbólico para ler arquivos confidenciais fora da sandbox no escopo), e a origem do clone é restrita a uma lista de permissões de protocolo;
- Todos os transportes/segredos com credenciais são criptografados em repouso e as credenciais da ponte são isoladas por execução;
- A distribuição de código aberto/hospedada elimina os recursos exclusivos do host por meio de uma regra de corte.
Em uma frase: cada ação explícita do usuário (instalar/conceder/enviar formulário/clicar em confirmar) é a credencial de consentimento, e todo consentimento está confinado ao limite que merece.
5. Ficando mais inteligente nas sessões: memória e autoevolução
A revisão da base também refez dois subsistemas que "tornam o agente mais inteligente quanto mais ele é usado", ambos seguindo a mesma disciplina de engenharia — desativado por padrão, limitado, observável.
Memória de sessão cruzada usa recuperação híbrida: pesquisa semântica vetorial + pesquisa por palavra-chave (BM25), mesclada via RRF (fusão de classificação recíproca) para evitar falha em um único canal; ele chega ao armazenamento local (com um índice de texto completo). A memória vem em dois tipos — as notas do próprio agente e o perfil de preferência do usuário — cada um com um limite de caracteres, verificado em busca de ameaças de injeção antes de escrever e injetado congelado no prompt do sistema no início de cada turno. Todo o sistema de memória serve apenas para fazer com que o agente entenda melhor o usuário atual; os dados sempre permanecem locais e o usuário pode visualizá-los, editá-los e exportá-los a qualquer momento nas configurações.
Autoevolução é uma biblioteca de habilidades privadas do agente (armazenada separadamente da biblioteca de habilidades compartilhadas da plataforma) mais uma camada de reflexão metacognitiva. O mecanismo decide se deve refletir por um conjunto de sinais ponderados: correção do usuário (peso mais alto), recuperação de um erro não trivial, complexidade da tarefa, uma fraqueza conhecida sendo acionada ou superada, ineficácia de habilidade... a reflexão só é acionada quando os sinais ponderados excedem um limite. A reflexão em si é uma tarefa periódica em segundo plano (aproximadamente uma rodada a cada 12 horas, um tempo de espera de várias horas, um substituto de vários dias) que usa um modelo pequeno e barato para ler um resumo da atividade recente e decidir se deve criar/corrigir uma habilidade e atualizar o "perfil de competência" do agente.
O ponto de segurança mais importante: a autoevolução é habilitada apenas para sessões que têm um agente explicitamente vinculado — a sessão padrão do comandante não evolui. O Reflection tem um limite de token duplo (contagem + total), a falha de um agente não bloqueia os outros e o custo por execução é extremamente baixo. Torne o agente mais inteligente, mas não o deixe fugir.
Filosofia de engenharia: complexidade conquistada — não simplifique-a
A equipe realizou diversas rodadas de revisão da arquitetura durante a refatoração, e uma conclusão continuou recorrente, e que vale a pena retirar por si só: distinguir "hipertrofia organizacional" de "complexidade adquirida" e abordar apenas a primeira.
- As múltiplas estratégias de mesclagem do mecanismo de sincronização, o loop de agente autoconstruído, o wrapper do provedor de múltiplas camadas, o limite do controle remoto móvel — tudo isso parece complexo, mas cada camada ganha seu sustento (consistência eventual de vários dispositivos, integração profunda, rotação de várias chaves, um limite final decidido pelo produto). "Simplificá-los" à força apenas perderia dados e confundiria as camadas.
- O que realmente deveria ser tocado são os "módulos divinos" e a duplicação local: extrair as funções puras sem estado do barramento de bate-papo em grupo inchado (montagem de prompt, ferramentas de comandante, a vez da CLI) e recolher o padrão de "diálogo de confirmação" que foi duplicado várias vezes em um componente compartilhado.
O que respalda esse tipo de julgamento é um conjunto de disciplinas rígidas escritas no documento de restrições do projeto: limite (processo único, IPC como único caminho, o tempo de execução só pode ser carregado dinamicamente), camadas (a direção de dependência de cada camada), fonte única de verdade (categorias, taxonomia de telemetria, domínios) e uma "auditoria imediata" obrigatória em cada commit voltado para prompt. O que permite que a fundação seja revisada sem entrar em colapso não é um projeto inteligente – são essas invariantes mantidas continuamente.
Encerramento
Junte as quatro linhas de passagem e o que esse refatorador básico transfere para o Orkas é uma base de agente que é autocontrolada, independente de provedor, orquestrada dinamicamente, aberta ao exterior e capaz de autoevoluir:
- Um tempo de execução em processo de mecanismo/adaptador de duas camadas que traz um conjunto completo de pontos fortes do agente de codificação — operações de arquivo, pesquisa local, ferramentas de sistema, multi-trabalhador paralelo, solução de longo horizonte — nativamente para o desktop e torna cada um deles de nível de produção;
- Uma camada de modelo multicamadas que mantém a conversa viva através da turbulência de chave/provedor/rede, tanto quanto possível;
- Uma orquestração multiagente no estilo de bate-papo em grupo, comandante no circuito, que troca o "planejamento estático" pela "decisão dinâmica" e, pela primeira vez, faz com que as transferências entre agentes pareçam naturais;
- Um ecossistema passando de um catálogo fechado para um host aberto, com pacotes externos, habilidades globais, MCP customizado e a ponte reversa, todos abertos — enquanto os pontos de estrangulamento do spawn não mudaram nem um centímetro;
- E memória e autoevolução que estão desativadas por padrão, limitadas e observáveis.
Os recursos podem ser adicionados um de cada vez, mas só vale a pena revisar seriamente uma base uma vez. Depois de concluído, tudo o que você construir será mais rápido - e esse é exatamente o resultado que esse refatorador buscava.
一个 AI agent 产品做到后面,最贵的不是功能,而是地基。 这篇文章讲 Orkas 在 1.0 这条发布线上做的一次底层重构——把模型调用、Agent 循环、多智能体编排和工具生态整体翻新——以及每个决定背后的取舍。
为什么要动地基
Orkas 是一个 local-first 的桌面 AI agent 工作台:所有 agent 工作都在用户本机的进程内跑,数据落在本地,再按需做端到端的云端同步。早期版本功能堆得很快——技能库、知识库、连接器、群聊式多 agent——但越往后越能感觉到:真正的瓶颈不在某个功能,而在三件"地基级"的事情上。
模型调用层若沿用对话式的老路,会被一串错误假设绑住。 把大模型当成"一问一答的聊天"来调用,背后藏着一堆对聊天合理、对 agent 不合理的默认:固定的输出 token 上限、串行的工具调用、藏起来的超时、写死的单一 provider。Agent 是一个要连续跑几十轮、动辄触达上下文窗口、需要并行读文件、还要随时被用户打断的长流程——这些默认每一条都会在生产里咬你一口。更要命的是,桌面端 agent 最值钱的那部分能力——细致的文件操作、本地检索、跑 shell、并行多 worker、长程任务求解——恰好都被这层假设挡在门外。
编排是"静态计划"的。 早期是一套 plan/DAG 引擎:先让模型把任务拆成一张计划图,再由执行器按图调度。听起来很工整,但 agent 的现实是高度动态的——读一个文件发现要改方向、一个子任务的结果决定下一步派给谁。把决策固化在一张预先生成的图里,意味着每次"计划赶不上变化"都要在执行器里打补丁。
生态是封闭目录。 技能只能来自官方 marketplace,连接器是硬编码的目录,用户机器上已有的外部 agent 工具对 Orkas 完全是黑盒。用户想接入一个第三方项目、自己的 MCP server、或者让本机已有的 agent 反过来调用 Orkas 的技能和知识库——架构上全都走不通。
这次重构的论点很简单:把 Agent 的地基重新握在自己手里。 具体落成四条相互交织的主线——自建进程内运行时、provider 无关的模型层、动态群聊编排、以及从封闭目录到开放宿主。下面逐条讲。
一、把一套完整的 coding agent 能力搬进桌面端
桌面端是 agent 的主场——这里有真实的文件系统、真实的 shell、真实的本地工具链。一个只会聊天的助手在这里是浪费环境;真正能发挥桌面优势的,是一套完整的 coding agent 能力:细致到字符范围的文件读写、跨文件的检索、跑 bash 与系统工具、并行开多个 worker、以及连续跑几十轮把一个复杂任务真正做完的长程求解能力。
重构的核心产物,就是为了把这套能力原生地搬进 Orkas 自己的进程——一个独立的、可被主进程动态加载的进程内 agent 运行时(代码里叫 core-agent)。它不是又一个对话封装,而是 Orkas 自己掌控的 agent 引擎。
关键的架构决定是把它切成两层:
- 引擎层(独立包):纯粹的 agent 机制——tool-calling 循环、流式事件、上下文压缩、错误分类与重试、provider 抽象、沙箱、技能扫描、记忆、自进化。它不知道 Orkas 的任何业务:不读业务数据目录、不认识会话文件格式、不碰 IPC。
- 适配层(主进程内):把引擎接进 Orkas——会话持久化、provider 轮换、工具权限、技能注册表、连接器、知识库、各类生成工具。它把引擎产出的原生事件翻译成 Orkas 自己的事件形状,业务层只看到稳定的接口。
这条 engine/adapter 的分界,是后面一切灵活性的根。引擎可以独立测试、独立演进;适配层可以放心地塞进 Orkas 特有的复杂度(轮换、冷却、沙箱、权限)而不污染引擎。团队的架构评审给它的结论是一句话:这是赚来的复杂度,别去合并它。
这套能力具体是什么
把运行时握在自己手里,目的不是炫技,而是让 agent 在桌面端真正"动手做事"。能力集大致分四组:
- 细致的文件操作与本地检索。
read_file 支持按字符范围读,PDF / Office 文档自动抽取文本;edit_file 做精确的"旧串 → 新串"替换、写前必须先读;write_file 落地产物并记账;stat_file 探测体量;search_files 按名字/通配定位、grep_files 跨文件搜内容。这一组让 agent 能像工程师一样在真实工作区里"翻代码、改文件",而不是只能整段吞吐。 - bash 与系统工具。 一个受控的 shell 执行器,带后台执行模式(长任务脱离当轮、日志落文件),危险操作经风险分级把关。桌面端 agent 的杠杆,很大一部分就来自能直接调动系统工具链。
- 并行多 worker。 一轮里互不依赖的只读工具会被并发执行;任务层面,指挥官还能把独立子任务并发扇出给多个 worker(详见第三节)。该并行的地方并行,是把"长程任务"压到可接受墙钟时间的关键。
- 长程推理与任务求解。 一个能连续跑几十轮、自己管理上下文、能从错误里恢复、不会卡死空转的循环——这是"把一件复杂事做完"和"答一个问题"之间的分水岭。
工程上怎么把它做到生产级
"自己写循环"听起来是给自己找麻烦,确实有维护成本。但它换来的是对 agent 完整生命周期的细粒度控制。这些控制不是抽象的,而是一组具体改进,每一个都对应上面某项能力在生产里"扛不扛得住":
真实上下文窗口 + 80% 才压缩。 引擎读到每个模型的真实上下文窗口(包括百万级窗口的模型),在用到 80% 时才触发压缩——而不是保守地在 60% 就开始丢掉四成有用上下文。配套还有一个"无收益压缩"的护栏:如果保留的尾部本身就占满了窗口(比如尾部躺着一个很大的文件读取结果),压缩释放不出空间,就只记一条告警跳过,绝不空转烧一次 summary 调用。长程任务能不能"记得住前面",全看这里。
相邻只读工具并行。 模型一轮里点了读文件、搜文件、联网查这几个互不依赖的只读工具,引擎会把相邻的可并行工具分到同一批并发执行;写工具是天然屏障,保持声明顺序。工具调用与结果的配对严格按声明顺序提交,所以并发不会破坏协议。最常见的只读工具一下子从串行变并行,整批的墙钟时间显著下降。
读后写 + 乐观并发控制。 编辑文件之前必须先读,引擎记录被读文件的基线;编辑时校验基线没漂移。并行 worker 同时改一个文件时,败者拿到一个明确的"已过期"错误,而不是把彼此的修改悄悄覆盖掉。多 worker 并行动同一份工作区,这道保险不可少。
中途打断即时折叠。 用户在 agent 跑到一半时又补了一句话,引擎会在工具循环的边界把这条排队消息折叠进当前这一轮的输入,而不是等它单独再起一轮。这让"边跑边纠偏"变成自然交互。
循环检测。 同一个工具调用连续重复,引擎会先轻推(第 3 次)再硬停(第 5 次),任何不同签名都会重置计数——分页/轮询这类合法的变参不会误伤。模型卡死时不再无声地烧 token。
去掉主轮输出的硬上限。 主轮输出不再被写死在一个很小的上限,长报告、大段编辑不会被无声截断;辅助调用(压缩、反思)仍保守地用小上限。
还有一个很"本地"的细节值得一提:中英文混排的 token 估算。纯中文会话用通用估算会被低估两三倍,引擎按字符类别区别对待中英文,压缩阈值因此才靠谱。这种东西,通用 SDK 不会替你想。
这些改进合起来回答了"为什么不直接用现成 SDK":因为桌面端 agent 最强的那部分能力,恰好都藏在 SDK 不暴露的那一层;要把它们做到生产级,循环必须握在自己手里。
二、让模型永远在线:Provider 的多层包装
模型层的目标是一句话:无论某个 key、某个 provider、某条网络出什么事,用户的这一轮对话都要尽量活下来。 为此适配层在引擎的 provider 抽象之上叠了几层包装——轮换、冷却、注册、外部适配。
最关键的设计是轮换器坐在 runner 的下方。引擎在调用 provider 之前就把用户消息写进了持久会话;如果在引擎层做重试/轮换,要么重复提交用户消息,要么得写一套会话回滚。把轮换器放在引擎下面,用户消息只写一次,"换一个候选重试"对会话状态完全透明。
轮换器的判断也很克制,核心是首个内容事件这条线:
- 在模型吐出任何实质内容(文本/工具调用)之前失败——可以安全地切到下一个候选;
- 一旦吐出了首个内容事件——停止轮换,错误向上抛,因为模型可能已经跑过了完整一轮,重来会重复副作用。
错误分类决定了"换、不换、还是重试"。鉴权失败、余额不足、限流、订阅过期这类账号级失败,标记冷却并轮换;连接被重置这类瞬时网络失败,不冷却、原地无状态重试几次再说;而畸形请求、内容策略、服务端 5xx——换个 key 也一样失败,直接放行不轮换。冷却是进程内、不持久的十分钟提示:它只是个短期信号,不值得为每次失败写盘,进程重启正好是重新探测的合理时机。
在 provider 的"花名册"这一端,重构把三类来源拉平成一个统一抽象:
- Orkas 托管 LLM:服务端代理,登录即用,服务端在文本/图像模型间路由;
- 用户自带 key:标准的主流大模型 provider;
- 外部直连适配器:一批需要直连或自带计费的模型,手工适配成同一个 provider 接口。
对上层来说,这一切只表现为一个稳定的 (provider, model) 对——轮换、冷却、外部适配全都藏在适配层里。
三、群聊即编排:从静态 plan-DAG 到 commander-in-the-loop
这是这次重构里最"换脑子"的一块。
旧模型是静态计划:模型先生成一张 plan/DAG,执行器按图跑。新模型把这张图整个拆掉,换成一套动态的、指挥官在环(commander-in-the-loop)的群聊编排。
它的隐喻是一个群聊房间:
- Commander(指挥官) 是房间的主持人,不是一个隐形的中间件;
- Agent workers 是房间里平等的一等成员;
- 所有交互都是异步消息,走唯一一条消息总线入队(不存在并行扇出的私有路径)。
指挥官的"调度"不是写在散文里的 @某某——LLM 在正文里写 @AgentA 只是训练数据里的 markdown,不可信。真正的调度信号是结构化的工具调用,而且重构后收敛成三个语义清晰的动作:
dispatch_to —— 派一个 agent 跑完、把结果交回,由指挥官综合。多个互不依赖的任务可以并发扇出。run_worker —— 指挥官自己拥有的子任务,同步拿回结果;匿名 worker 是指挥官的"手"(对用户不可见),具名 worker 是可见的专家。hand_off_to —— 把对话交给 agent,指挥官退场,agent 直接回用户、这一轮不再综合。
为什么是群聊,而不是 orchestrator 或子代理树
把多 agent 做成群聊,带来几个传统编排器/子代理树拿不到的好处:
- 可见性切片。 每条消息只追加到"能看到它的人"的切片里。Agent worker 启动时只重放自己的切片,别的 agent 的大段输出不会污染它的上下文。指挥官看得见全部。
- 状态极简。 整个编排的核心状态只有"当前发言权归谁"和一个轻量的任务台账。没有 DAG,没有复杂状态机。
- 天然可重放、可同步。 消息按时间戳自然排序,重新加载、跨设备同步都直接落在消息流上。这套架构曾用于移动端远程控制原型:所有 agent 计算都在桌面端跑,移动端只做镜像呈现。当前公开桌面版本未启用 iOS 远程控制中继。
团队的架构评审对这一点的判断也很直接:群聊式的总线加上指挥官在环,就是 Orkas 的多 agent 形态;在进程内再叠一套并行子代理派发,反而会违反"只有一条群聊派发路径"的不变量。
这一版的新东西:可交互的 hand-off
重构在这条线上最新的一块拼图,是可交互的 agent 交接。
痛点很具体:一个"家教"型 agent 教了用户一轮,用户想接着追问,系统却强制把发言权收回给指挥官,用户被迫每句话都重新 @ 一遍这个 agent。
解决方案是服务端权威的发言权(floor)+ 模型决定的接收者:
- 发言权落成一个持久化的状态字段,跨重载保存,并搭着既有的状态变更事件自动同步到各端——不需要新事件类型。
- 指挥官用
hand_off_to 把发言权交给一个可交互 agent 后,用户后续的"无 @"消息直达该 agent,直到 agent 主动交还或用户重新点名指挥官。 - Agent 用一个
<handback /> 标记交还控制权;解析时严格校验真匹配(避免把散文里偶然出现的 <handback 误判成交接)。 - 交还时若有未完成的任务台账,指挥官会从台账接着往下做。
配套还有一个体验上的修正——指挥官循环气泡。指挥官一轮里"派活 → 读结果 → 再派活"的循环,过去被压成一个气泡,重载时还会乱序跳到底部。重构把一轮在每次可见派发的边界处切成多个段(segment),每段是独立消息、时间戳递增——用户第一次能看见指挥官在"循环编排",重载顺序也对了。
最后,两条贯穿始终的安全网:group abort 是所有 actor 唯一的停止路径(用户一按 Stop,所有 worker 的中止信号都被掐掉,连匿名子 worker 都有兜底匹配);以及前面提到的 interrupt-steer,把用户的中途插话折进当前轮。
四、从封闭目录到开放宿主
如果说前三条是把地基打牢,这一条是把房子的门窗全部打开——把 Orkas 从一个封闭目录变成一个开放宿主——同时一寸不让地守住安全边界。
重构系统性地拆掉了几处"封闭"的卡点:
外部包(External packages)。 用户给一个仓库地址,Orkas 把它原样克隆成一个文件夹托管在本地,绝不规范化、不重写、不同步到云端(因为里面有第三方依赖目录)。一个独立的命令行工具负责安装/更新/启停的全生命周期,扫描出它是"技能形"(带技能描述文件)还是"CLI 形"(带可执行入口),把元数据写进一个包目录之外的注册表(这样后续拉取更新永远不会冲突)。依赖安装走"问一次、记住"的二步确认;可执行入口生成 shim 注入到 bash 工具的 PATH,模型于是能直接调用这些第三方 CLI。
多根技能加载。 技能执行的唯一入口从只认两个根,扩成 自定义 / marketplace / 外部包 / 全局 四层,按优先级解析;外部包里的脚本优先用包内自带的依赖环境。这是回归风险最高的一处 choke point,配了完整的 fixture 矩阵。
全局技能互操作。 直接读取用户机器上其它 agent 工具已有的全局技能目录,做到技能层面的互通——用户在一处沉淀的技能,在 Orkas 里也能用。用户把技能放进这些目录的动作本身就是授权,所以默认启用,但留了总开关。这些第三方技能描述是不可信的 prompt 注入面,所以它们走"开放层"加载器、只对指挥官可见,结构上不可能进入 agent 的技能白名单。
用户自配置 MCP。 连接器不再是硬编码目录。用户能加任意 MCP server——远程 HTTP 形态(低风险)或本地子进程形态(高风险)。表单本身就是同意面(用户亲手敲进去的命令逐字展示),传输配置(含密钥)整体进加密存储,自定义实例一律带固定前缀,永远不可能冒充目录里的官方连接器。
反向桥接:让本机的外部 agent 反过来感知 Orkas。 这是最有意思的一块。用户机器上已有的外部 agent 工具,过去对 Orkas 是黑盒;现在 Orkas 在派发它们时注入一个桥接通道,让它们能反过来列举/读取/运行 Orkas 的技能、调用连接器、检索知识库。桥接走本地进程间通道(不开网络端口),用每次运行独立、随运行结束即销毁的一次性凭据鉴权。每一次有外部副作用的连接器调用都过用户确认对话框——不是按工具名启发式判断读写(那会失之于过松),而是每个 (agent, 连接器) 一次确认、可选"始终允许"。
长尾编码姿态。 指挥官的决策树新增一条分支:没有匹配的 agent/技能/连接器时,评估直接用 bash + 一段脚本解决,这一轮做掉、验证输出,并可提议把它沉淀成一个自定义技能。配套有 bash 后台执行(长任务脱离当轮、日志落文件)和用户授权目录。
开放,但不松手
把门窗打开最怕的是漏风。这次重构的纪律是:所有"危险动作"的 spawn choke point 一个都不动。 MCP 只从一个地方启动,技能执行只走一个 runner,bash 只过一个沙箱执行器。在此之上叠了几层纵深:
- 文件操作一律过路径沙箱(工作区 + 当前附件 + 用户显式授权的目录),凭据目录、系统目录、Orkas 自身目录不可被授予;
- 危险 bash(外泄、破坏性删除、提权、敏感路径)触发权限确认,决策分"仅此一次 / 本次运行 / 拒绝",日志只记类别和长度、绝不记命令文本;
- 外部包安装对软链成员 fail-closed 直接拒装(防止借软链把沙箱外的敏感文件读进来),克隆来源限定协议白名单;
- 所有携带凭据的传输/密钥一律加密存储,桥接凭据随运行隔离;
- 开源/托管发行版按裁剪规则去掉宿主专属能力。
一句话:用户的每一个显式动作(安装 / 授权 / 提交表单 / 点确认)就是同意的凭据,而每一个同意都被限制在它该有的边界里。
五、跨会话变聪明:记忆与自进化
地基翻新顺带把两个"让 agent 越用越聪明"的子系统也重做了,并且都遵循同一条工程纪律——默认关闭、上限有界、可观测。
跨会话记忆用混合检索:向量语义检索 + 关键词检索(BM25),用 RRF(倒数排名融合)合并,避免单一通道失效;落在本地存储里(带全文索引)。记忆分两类——agent 自己的笔记和用户偏好画像,都有字符上限、写入前过注入威胁扫描,并在每轮开始冻结注入进系统提示。整套记忆只服务于让 agent 更懂当前用户,数据始终在本地,用户在设置里可随时查看、编辑、导出。
自进化是 agent 私有的技能库(和平台共享技能库分开存放)加一套元认知反思。引擎按一组加权信号判断要不要反思:用户纠正(权重最高)、从非平凡错误中恢复、任务复杂度、已知弱点被触发或被克服、技能失效……信号加权超过阈值才反思。反思本身是一个后台周期任务(约 12 小时一轮、数小时冷却、多日兜底),用便宜的小模型读最近的活动摘要,决定要不要创建/修补一条技能、更新 agent 的"能力画像"。
安全上最重要的一条:只有显式绑定了 agent 的会话才开启自进化——默认的指挥官会话不进化。反思有 token 双重上限(条数 + 总量)、单 agent 失败不阻塞其他、单次成本被压到极低。让 agent 变聪明,但不让它失控。
工程心法:赚来的复杂度,别去简化它
重构期间团队做了多轮架构评审,有一条结论反复出现,值得单独拎出来:区分"组织肥大"和"赚来的复杂度",只动前者。
- 同步引擎的多种合并策略、自建的 agent 循环、provider 的多层包装、移动端远程控制的边界——这些看着复杂,但每一层都在挣自己的钱(多设备最终一致性、深度集成、多 key 轮换、产品决定的端边界)。强行"简化"只会把数据弄丢、把分层弄脏。
- 真正该动的是"上帝模块"和局部重复:把臃肿的群聊总线里那些无状态纯函数(prompt 组装、指挥官工具、CLI 轮)拆出去,把重复了多遍的"确认对话框"模式收成一个通用件。
支撑这种判断力的,是一套写进项目约束文档的硬纪律:boundary(单进程、IPC 唯一通路、运行时只能动态加载)、layering(各层的依赖方向)、单一真相源(类目、遥测口径、域名)、以及面向 prompt 的提交必须带"prompt audit"。地基能翻新而不塌,靠的不是某个聪明的设计,而是这些不变量被持续守住。
结语
把这四条线接起来看,这次底层重构给 Orkas 换上的,是一套自己掌控、provider 无关、动态编排、对外开放、还能自我进化的 agent 地基:
- 一个 engine/adapter 两层的进程内运行时,把一整套 coding agent 强项能力——文件操作、本地检索、系统工具、并行多 worker、长程求解——原生搬进桌面端,并逐条做到生产级;
- 一个多层包装的模型层,让对话在 key/provider/网络抖动下尽量活下来;
- 一套群聊式、指挥官在环的多 agent 编排,把"静态计划"换成"动态决策",并第一次让 agent 之间的交接变得自然;
- 一个从封闭目录走向开放宿主的生态,外部包、全局技能、自定义 MCP、反向桥接全部打通,而 spawn choke point 一寸未让;
- 以及默认关闭、有界、可观测的记忆与自进化。
功能可以一个个加,但地基只值得认真翻一次。翻完之后,上面盖什么都更快了——这正是这次重构想要的结果。
AI agent プロダクトが成熟していくと、最も高くつくのは機能ではなく土台です。 この記事では、Orkas が 1.0 リリースラインで行った基盤リファクタリング、つまりモデル呼び出し、Agent loop、マルチ Agent 編成、tool ecosystem を丸ごと刷新した理由と、その判断の裏側を説明します。
なぜ土台に手を入れるのか
Orkas は local-first の desktop AI agent workspace です。すべての agent work はユーザー自身のマシン上の process 内で走り、データはローカルに置かれ、必要に応じて end-to-end の cloud sync を行います。初期バージョンでは機能追加が速く進みました。skill library、knowledge base、connectors、group-chat-style multi-agent。しかし進めるほど、ボトルネックは単一機能ではなく、三つの「土台レベル」の問題にあることがはっきりしました。
モデル呼び出し層が会話型の古い道を進むと、agent には合わない前提に縛られる。 大きなモデルを「一問一答の chat」として呼ぶと、chat には妥当でも agent には合わない default が紛れ込みます。固定された output token 上限、直列の tool call、見えない timeout、単一 provider への固定。Agent は何十 turn も連続して走り、context window に触れ、file を並列に読み、いつでもユーザーに止められる長い flow です。これらの default はすべて production で噛みついてきます。さらに悪いことに、desktop agent の最も価値ある能力、細かい file 操作、local search、shell 実行、parallel multi-worker、long-horizon task solving は、まさにこの層の前提で閉じ込められていました。
編成が「静的 plan」だった。 早期版は plan/DAG engine でした。まずモデルに task を plan graph へ分解させ、executor がその graph に沿って dispatch します。整って見えますが、agent の現実はかなり動的です。file を一つ読んだら方針が変わる、ある sub-task の結果で次に誰へ渡すかが決まる。判断を事前生成の graph に固定すると、「計画が現実に追いつかない」たびに executor 側へ patch を積むことになります。
ecosystem が閉じた catalog だった。 Skills は official marketplace からしか来られず、connectors は hard-coded catalog、ユーザーのマシンに既にある外部 agent tools は Orkas から見ると完全な black box でした。第三者 project、自分の MCP server、または既存の外部 agent から Orkas の skills や knowledge base を呼びたい。以前の architecture では、どれも通り道がありませんでした。
今回の refactor の論点は単純です。Agent の土台を自分たちの手に取り戻す。 具体的には、四つの線が絡み合っています。自前の in-process runtime、provider に依存しない model layer、動的な group-chat orchestration、そして closed catalog から open host への移行です。順番に見ていきます。
1. 完整な coding-agent 能力を desktop に持ち込む
Desktop は agent の主戦場です。ここには本物の file system、本物の shell、本物の local toolchain があります。chat しかできない assistant は、この環境を活かしきれていません。desktop の強みを本当に使うには、完整な coding-agent capability set が必要です。character range まで指定できる file read/write、cross-file search、bash と system tools の実行、複数 worker の parallel 実行、そして複雑な task を本当に終わらせるまで何十 turn も走れる long-horizon solving です。
Refactor の中心成果は、この能力群を Orkas 自身の process に native に持ち込む ことでした。独立して動的 load できる in-process agent runtime、code 上では core-agent と呼んでいるものです。これは単なる chat wrapper ではありません。Orkas が自分で control する agent engine です。
重要な architecture 判断は、これを 二つの層に分ける ことでした。
- Engine layer(独立 package): 純粋な agent machinery。tool-calling loop、streaming events、context compaction、error classification と retry、provider abstraction、sandbox、skill scanning、memory、self-evolution。Orkas の business を何も知りません。business data directory を読まず、会話 file format を知らず、IPC に触れません。
- Adapter layer(main process 内): engine を Orkas へつなぎます。session persistence、provider rotation、tool permissions、skill registry、connectors、knowledge base、各種 generation tools。engine の native events を Orkas 独自の event shape へ翻訳し、business layer からは stable interface だけが見えるようにします。
この engine/adapter の境界が、その後の柔軟性の根です。Engine は独立して test し evolve できます。Adapter は rotation、cooldown、sandbox、permissions といった Orkas 固有の複雑さを吸収できますが、その複雑さを engine に汚染させません。チームの architecture review はこれを一文でまとめました。これは earned complexity であり、無理に merge してはいけない。
具体的にどんな能力か
runtime を自分の手に持つ目的は、派手さではありません。agent が desktop 上で本当に「手を動かせる」ようにすることです。能力は大きく四つに分かれます。
- 細かい file 操作と local search。
read_file は character range 指定に対応し、PDF / Office documents から text を自動抽出します。edit_file は正確な「old string → new string」置換を行い、write 前に read を要求します。write_file は artifact を保存して記録し、stat_file は size を確認します。search_files は name/glob で探し、grep_files は file 内容を横断検索します。この一式により、agent は whole block を飲み込んで吐くのではなく、engineer のように実 workspace を調べて修正できます。 - Bash と system tools。 sandboxed shell executor は background execution mode を持ちます。長い task は current turn から離して log を file に落とせます。dangerous operations は risk grade に応じて gate されます。desktop agent の leverage の大きな部分は、system toolchain を直接使えることにあります。
- Parallel multi-worker。 一つの turn 内では、依存しない read-only tools が concurrent に実行されます。task level では、commander が独立 sub-task を複数 worker へ fan-out できます。安全なところを parallel にすることが、long-horizon task の wall-clock time を現実的な長さに圧縮します。
- Long-horizon reasoning と task solving。 何十 turn も走り、context を自分で管理し、error から回復し、同じ場所で空回りしない loop。これが「複雑な仕事を終える」と「質問に答える」の境目です。
Production grade にするための工程
「自分で loop を書く」と聞くと、面倒を背負うように見えます。実際、maintenance cost はあります。しかしその代わり、agent の lifecycle 全体を細かく control できます。この control は抽象論ではなく、production で各能力が持ちこたえるかどうかに直結する具体的な改善です。
実 context window + 80% で compaction。 Engine は各 model の実際の context window を読みます。million-token window の model も含みます。そして usage が 80% に達したときだけ compaction を trigger します。保守的に 60% で始めて有用な context の四割を捨てるのではありません。さらに「no-gain compaction」の guardrail があります。保持すべき tail 自体が window を埋めている場合、例えば大きな file read result が tail にある場合、compaction しても空きは増えません。そのときは warning を記録して skip し、summary call を空回りさせません。長い task が前の文脈を覚えていられるかは、ここにかかっています。
隣接 read-only tools を parallel に実行。 モデルが一つの turn で read-file、search-file、web lookup など互いに独立した read-only tools を並べたら、engine は隣接する parallelizable tools を batch 化して concurrent に実行します。write tools は自然な barrier になり、宣言順を保ちます。tool call と result は宣言順に commit されるため、concurrency は protocol を壊しません。最もよく使う read-only tools が serial から parallel になり、batch 全体の wall-clock time が目に見えて下がります。
Read-before-write + optimistic concurrency control。 file を edit する前には必ず read します。engine は読んだ file の baseline を記録し、edit 時に baseline が drift していないか確認します。parallel workers が同じ file を同時に変更した場合、敗者は明確な stale error を受け取り、互いの変更を silent overwrite しません。複数 worker が同じ workspace を触るなら、この保険は必須です。
途中 interrupt の即時 folding。 agent が走っている最中にユーザーが一言追加した場合、engine は tool-loop boundary でその queued message を現在の turn の input に折り込みます。別 turn として待たせません。これにより「走らせながら軌道修正する」ことが自然な interaction になります。
Loop detection。 同じ tool call が連続して繰り返された場合、engine は 3 回目で軽く促し、5 回目で hard stop します。signature が少しでも違えば count は reset されます。pagination や polling のような正当な変化は誤検知しません。モデルが詰まったとき、token を黙って燃やし続けることがなくなります。
main turn output の hard cap を外す。 主 turn の output は小さな固定上限に縛られなくなりました。長い report や大きな edit が silent truncation されません。compaction や reflection のような auxiliary call は、引き続き保守的な小さな上限を使います。
もう一つ、とても local な detail があります。中国語と英語が混ざる text の token estimation です。generic estimate は純中国語会話を二倍から三倍ほど undercount します。engine は character class ごとに中国語と英語を区別し、compaction threshold が現実に近くなるようにしています。こういうことは generic SDK が代わりに考えてはくれません。
これらを合わせると、「なぜ既製 SDK を使わないのか」への答えになります。desktop agent の最も強い能力は、SDK が露出しない層に隠れているからです。それを production grade にするには、loop を自分で握る必要があります。
2. モデルを常に online に保つ:Provider の多層 wrapper
Model layer の目的は一文です。ある key、ある provider、ある network に何が起きても、可能な限りユーザーのこの turn を生き残らせる。 そのため adapter layer は、engine の provider abstraction の上に rotation、cooldown、registration、external adaptation という数層の wrapper を重ねました。
最も重要な設計は、rotator を runner の下に置くことです。Engine は provider を呼ぶ前に、user message を persistent session に書き込みます。もし engine layer で retry/rotation を行うと、user message を重複 submit するか、session rollback を作る必要があります。rotator を engine の下に置けば、user message は一度だけ書かれ、「別 candidate で retry する」ことは session state から完全に透明になります。
rotator の判断も抑制的です。中心にあるのは first content event という線です。
- モデルが substantive content、つまり text や tool call を一切出す前に失敗した場合は、次の candidate へ安全に切り替えられます。
- first content event が出た後は rotation を止め、error を上へ投げます。モデルがすでに一 turn を実行している可能性があり、やり直すと副作用が重複するからです。
Error classification が「rotate する、rotate しない、retry する」を決めます。auth failure、insufficient balance、rate limiting、expired subscription のような account-level failure は cooldown を付けて rotate します。connection reset のような transient network failure は cooldown せず、その場で stateless retry を数回試します。malformed request、content policy、server 5xx は key を変えても同じように失敗するため、そのまま通します。cooldown は十分間の in-process, non-persistent な hint です。短期 signal にすぎないので失敗のたびに disk へ書く価値はなく、process restart は再探査にちょうどよいタイミングです。
Provider の roster 側では、三つの source を一つの abstraction へ平らにしました。
- Orkas-managed LLM: server-side proxy。sign in すれば使え、server が text/image model の間を route します。
- 自分のキーを持ち込む: 主要な LLM プロバイダーをユーザー自身の key で使います。
- 外部の直接接続アダプター: 直接接続や独自課金が必要な model 群を、同じ provider interface へ手作業でアダプトします。
上位層から見ると、すべては安定した (provider, model) pair に見えます。rotation、cooldown、external adaptation は adapter layer の中に隠れています。
3. Group chat が編成になる:static plan-DAG から commander-in-the-loop へ
今回の refactor で最も「考え方を入れ替える」部分がここです。
旧モデルは static planning でした。モデルがまず plan/DAG を生成し、executor が graph に沿って実行する。新モデルはこの graph を丸ごと外し、dynamic な commander-in-the-loop group-chat orchestration に置き換えました。
比喩は group-chat room です。
- Commander は room の host であり、見えない middleware ではありません。
- Agent workers は room の中の first-class members です。
- すべての interaction は asynchronous messages で、一つの message bus に入ります。parallel fan-out 用の private path はありません。
commander の dispatch は、本文に書かれた @somebody ではありません。LLM が body text に @AgentA と書くのは、training data に由来する markdown にすぎず、信頼できません。本物の dispatch signal は structured tool call です。refactor 後は、三つの semantic に明確な action に収束しました。
dispatch_to: agent に task を完了まで走らせ、result を返させ、commander が統合します。互いに独立した task は concurrent fan-out できます。run_worker: commander 自身が所有する sub-task です。result は synchronous に戻ります。anonymous worker は commander の「手」でユーザーには見えず、named worker は visible specialist です。hand_off_to: conversation を agent に手渡します。commander は退場し、その turn では agent が直接 user に答え、上から synthesis しません。
なぜ orchestrator や sub-agent tree ではなく group chat なのか
Multi-agent を group chat として作ると、従来の orchestrator / sub-agent tree では得にくい利点があります。
- Visibility slices。 各 message は「見えるべき相手」の slice にだけ追加されます。Agent worker が起動すると、自分の slice だけを replay します。別 agent の大きな output がその context を汚しません。commander はすべてを見ます。
- 状態が最小。 orchestration の core state は「今の floor を誰が持っているか」と軽量な task ledger だけです。DAG も複雑な state machine もありません。
- 自然に replay でき、sync できる。 Messages は timestamp で自然に並びます。reload も cross-device sync もその message stream にそのまま乗ります。mobile はこの stream で remote control します。すべての agent compute は desktop 側で走り、mobile は mirror 表示に徹するため、特別な orchestration protocol は不要です。
チームの architecture review もここは明確でした。group-chat bus と commander-in-the-loop こそが Orkas の multi-agent shape です。process 内にさらに parallel sub-agent dispatch path を重ねると、「group-chat dispatch path は一つだけ」という invariant を壊します。
この版で入った新要素:interactive hand-off
この線で新しく入った一番大きな piece は、interactive agent hand-off です。
痛点は具体的でした。「tutor」型 agent が一 turn 教えたあと、ユーザーは続けて質問したい。しかし system が floor を commander に戻してしまうため、ユーザーは毎回その agent を @ で呼び直さなければならない。
解決策は server-authoritative floor + model-decided recipient です。
- floor は persistent state field になり、reload をまたいで保存されます。既存の state-change event に乗って各端へ auto-sync されるため、新しい event type は要りません。
- commander が
hand_off_to で interactive agent に floor を渡した後、ユーザーの後続の「@ なし」message は、その agent に直接届きます。agent が自分で返すか、ユーザーが commander を再指名するまで続きます。 - agent は
<handback /> marker で control を返します。parser は本当に match しているかを厳密に確認し、散文に偶然 <handback が出たものを hand-off と誤読しません。 - hand-back 時に unfinished task ledger があれば、commander は ledger から続きを拾います。
合わせて体験面の修正も入りました。commander loop bubbles です。commander が一 turn の中で「dispatch → result を読む → また dispatch」と回る loop は、以前は一つの bubble に潰され、reload すると順序も下に飛ぶことがありました。refactor では、一つの turn を可視 dispatch の boundary ごとに複数 segment へ切り、各 segment を timestamp が増える独立 message にしました。ユーザーは初めて、commander が「編成 loop を回している」ことを見られるようになり、reload order も正しくなります。
最後に、全体を貫く safety net が二つあります。group abort はすべての actor の唯一の stop path です。ユーザーが Stop を押した瞬間、全 worker の abort signal が切られ、anonymous sub-worker も fallback match で覆われます。もう一つは前述の interrupt-steer で、ユーザーの mid-run interjection を current turn へ折り込みます。
4. Closed catalog から open host へ
前の三本が土台を固める話なら、この一本は家の扉と窓を開く話です。Orkas を closed catalog から open host に変える。ただし security boundary は一寸も緩めません。
Refactor は、いくつかの「閉じた」choke point を systemically に外しました。
External packages。 ユーザーが repository address を渡すと、Orkas はそれを local folder にそのまま clone して hostします。normalize せず、rewrite せず、cloud sync もしません。中には third-party dependency directories が含まれるからです。独立した command-line tool が install/update/start-stop lifecycle を担当し、それが「skill-shaped」なのか「CLI-shaped」なのかを scan します。metadata は package directory の外側にある registry へ書きます。これにより後続の pull update と衝突しません。dependency install は「一度聞き、覚える」二段階 confirmation を通します。executable entries は shim として生成され、bash tool の PATH に注入されます。モデルは third-party CLI を直接呼べます。
Multi-root skill loading。 skill execution の single entry point は、二つの root だけを見る形から、custom / marketplace / external package / global の四 tier を priority 付きで resolve する形になりました。external package の script は package 内の dependency environment を優先します。ここは regression risk が最も高い choke point なので、fixture matrix を厚く置きました。
Global skill interop。 ユーザーのマシン上で他の agent tools が既に使っている global skill directories を直接読みます。一つの場所で蓄積した skill が Orkas でも使えるようになります。ユーザーがそこへ skill を置いた行為そのものが authorization なので default on ですが、master switch は残しています。第三者 skill description は untrusted prompt-injection surface なので、「open-tier」loader を通り、commander にだけ見え、構造上 agent の skill allowlist には入れません。
User-configured MCP。 connectors は hard-coded catalog ではなくなりました。ユーザーは任意の MCP server を追加できます。remote HTTP 形態は低リスク、local subprocess 形態は高リスクです。form 自体が consent surface で、ユーザーが手で入力した command は逐字表示されます。transport config、secret を含むものは encrypted storage に入り、custom instance には常に fixed prefix が付くため、official connector を偽装できません。
Reverse bridge: 外部 agent が Orkas を見られるようにする。 ここが最も面白い部分です。ユーザーのマシン上にある外部 agent tools は、以前 Orkas から見ると black box でした。今は Orkas がそれらを dispatch するときに bridge channel を注入し、外部 agent が逆向きに Orkas の skills を list/read/run し、connectors を呼び、knowledge base を検索できます。bridge は local inter-process channel で動き、network port は開きません。run ごとに独立し、run 終了と同時に破棄される one-time credential で認証します。external side effect を伴う connector call はすべて user confirmation dialog を通ります。tool name から read/write を heuristics で判断するのではなく、(agent, connector) ごとに一度確認し、optional に「always allow」を選べます。
Long-tail coding posture。 commander の decision tree に branch が増えました。matching agent/skill/connector がない場合、bash + short script で直接解けるか評価し、この turn で解き、output を verify し、必要なら custom skill として crystallize する提案をします。background bash execution と user-granted directories もこれに伴います。
Open だが、手放さない
扉を開くときに最も怖いのは、境界が漏れることです。今回の discipline は明確です。dangerous actions の spawn choke point は一つも動かさない。 MCP は一か所からだけ起動し、skill execution は一つの runner だけを通り、bash は一つの sandbox executor だけを通ります。その上に、何層もの defense を重ねます。
- File operations は必ず path sandbox を通ります。workspace、current attachments、ユーザーが明示的に grant した directories が対象です。credential directories、system directories、Orkas 自身の directories はgrant できません。
- Dangerous bash、つまり exfiltration、destructive deletion、privilege escalation、sensitive paths は permission confirmation を trigger します。decision は「just this once / for this run / deny」に分かれ、log は category と length だけを記録し、command text は記録しません。
- External-package install は symlink members を持つ package を fail-closed で拒否します。symlink で sandbox 外の sensitive files を scope に読み込ませるのを防ぐためです。clone source は protocol allowlist で制限します。
- credential を含む transport/secrets はすべて encrypted at rest になり、bridge credentials は run ごとに隔離されます。
- open-source / hosted distribution では、host-exclusive capabilities が trimming rule によって取り除かれます。
一文で言えば、ユーザーの明示的な行為、install / grant / form submit / confirm click が consent の credential であり、その consent は必ず相応の boundary に閉じ込められます。
5. Session をまたいで賢くなる:memory と self-evolution
Foundation overhaul に合わせて、「使うほど agent が賢くなる」二つの subsystem も作り直しました。どちらも同じ engineering discipline に従います。default off、bounded、observable です。
Cross-session memory は hybrid retrieval を使います。vector semantic search と keyword search(BM25)を行い、RRF(reciprocal rank fusion)で merge して、単一 channel の失敗を避けます。storage は local にあり、full-text index を持ちます。Memory は二種類です。agent 自身の notes と user-preference profile。どちらも character cap があり、write 前に injection threat scan を通し、各 turn 開始時に system prompt へ frozen-injected されます。この memory system は現在のユーザーを agent がより理解するためだけにあり、data は常に local にあります。ユーザーは settings から view、edit、export できます。
Self-evolution は agent-private skill library、platform shared skill library とは別のもの、と metacognitive reflection の layer です。Engine は weighted signals で reflection すべきか判断します。user correction が最も高い weight を持ち、non-trivial error からの recovery、task complexity、known weakness の trigger または overcome、skill ineffectiveness なども signal になります。weighted signals が threshold を超えたときだけ reflection します。reflection 自体は background periodic task です。約 12 時間ごとの cycle、数時間の cooldown、多日 fallback を持ち、cheap small model が recent activity summary を読み、skill を create/patch するか、agent の competence profile を更新するかを決めます。
最も重要な safety point は、self-evolution は agent が明示的に bind された session でだけ有効 ということです。default commander session は evolve しません。reflection は token の二重上限、件数と総量、を持ち、一つの agent の失敗が他を止めることもありません。single run cost は極小に押さえられます。agent を賢くする。ただし暴走させない。
Engineering philosophy: earned complexity を無理に単純化しない
Refactor 中、チームは何度も architecture review を行いました。その中で繰り返し出た結論があります。organizational hypertrophy と earned complexity を区別し、前者だけを触る。
- sync engine の複数 merge strategy、自前の agent loop、provider の multi-layer wrapper、mobile remote control の boundary。これらは複雑に見えますが、各 layer が自分の価値を稼いでいます。multi-device eventual consistency、deep integration、multi-key rotation、product が決めた end boundary。無理に「simplify」すれば、data を失い、layering を濁らせます。
- 本当に触るべきなのは god modules と local duplication です。膨らみすぎた group-chat bus から stateless pure functions、prompt assembly、commander tools、CLI turn を切り出し、何度も重複していた confirmation dialog pattern を shared component にまとめる。
この判断力を支えるのは、project constraints document に書かれた hard disciplines です。boundary、single process、IPC が唯一の通路、runtime は dynamic load のみ。layering、各 layer の dependency direction。single source of truth、categories、telemetry taxonomy、domains。prompt-facing commit には prompt audit が必要。土台を作り替えても崩れないのは、どこか一つの clever design のおかげではなく、これらの invariant が継続して守られているからです。
まとめ
四つの線をつなげて見ると、この ground-up refactor が Orkas に入れ替えたのは、self-controlled、provider-agnostic、dynamically orchestrated、外部に open、そして self-evolve できる agent foundation です。
- engine/adapter の二層 in-process runtime が、file operations、local search、system tools、parallel multi-worker、long-horizon solving という coding-agent の強みを desktop に native に持ち込み、それぞれを production grade にした。
- multi-layered model layer が、key/provider/network の揺れの中でも conversation をできるだけ生かす。
- group-chat-style、commander-in-the-loop の multi-agent orchestration が、「static planning」を「dynamic decision」に置き換え、agent 間 hand-off を初めて自然なものにした。
- closed catalog から open host へ移る ecosystem が、external packages、global skills、custom MCP、reverse bridge をすべて開いた。一方で spawn choke point は一寸も動かさなかった。
- memory と self-evolution は default off、bounded、observable に保たれている。
機能は一つずつ追加できます。しかし foundation は、一度だけ真剣に作り替える価値があります。作り替えた後は、その上に何を載せても速くなります。今回の refactor が目指した結果は、まさにそれです。