Arslan Docs Based on v0.1.63English

Arslan, explained.Design, implementation and evolution, from the first prototype to v0.1.63

A local-first, open-source personal AI assistant for the Mac: its product choices, how it runs, its data and permissions, and the engineering lessons of more than fifty releases. The text separates what shipped, historical designs and future directions; every claim links to the v0.1.63 source.

2026.06.07 →

Earliest traceable development in Git
not the first idea or the open-source date

64 version tags

v0.1.0–v0.1.63, none missing
63 of them public releases

One core shift

Expert personas → one agent loop
capabilities kept, a shorter path

01 / OverviewConnecting plain language to real work

Arslan is an open-source, Mac-first, local-first personal AI assistant for getting work done. You describe what you want done; Arslan assembles the context, calls a model, uses tools, records what it did, and delivers an answer or a file you can check. Chat, memory, file handling, the terminal, the web, some native Mac apps and background work all sit behind one entry point.

The author builds and iterates on it alone, with AI assistance — what is often called vibe coding. This document goes from the product's goals down to the actual code boundaries: why it is designed this way, how it runs, and how it changed from the first prototype to v0.1.63.

Understand the task

From this turn's request, recent conversation, the personal memory it is allowed to use and relevant material, work out what information and tools the next step needs.

Act and observe

The model proposes a structured action, code validates its arguments and permissions, the tool returns a real result, and the model decides whether to continue, change approach or wrap up.

Deliver a result you can check

Files, tool returns, task state and sources are the evidence for the result. You can watch progress, handle approvals, stop a task, or carry on from what is already recorded.

One entry point, several kinds of work

Kind of taskTypical workConditions and limits of the result
Research and readingSearch the web, extract content, read attachments, summarise with sourcesDepends on whether pages are reachable, how completely they are extracted, and the model; citations can be checked but do not by themselves prove a conclusion
Files and deliverablesProcess text or data in the workspace, run conversion commands, produce files that openWhat can be read or written depends on the tool, the workspace and permissions; what is recorded as saved should match the real file
Working on the MacRead and operate supported apps through system interfaces, Shortcuts, Apple Events or HandsEach path has its own system permissions and app compatibility; sensitive actions may need extra confirmation
Ongoing workKeep long work in the chat, or hand it to the background when you or the model choose to; run scheduled tasks (which run no terminal commands in 0.1.63); receive progress and resultsNeeds the local process to be running; when the Mac sleeps, is offline or is off, do not expect cloud-service availability
Working the way you doRemember how to address you, format preferences, project rules and methods proven on this machineMemory has scope, sensitivity, provenance and use permissions; learned practices never add permissions to a tool

What "local-first" actually means

Local-first describes where data is primarily stored and where work runs: the desktop interface, the local service, the database, the workspace and the Mac tools run mainly on your machine. You configure the model. With a cloud model, the conversation, excerpts of material and any memory you allowed for that use are sent to that model provider. A compatible local model endpoint can also be connected, but tool calling and vision still depend on the model and how the service implements them.

So Arslan's architecture has to answer two questions at once: which machine the work happens on, and what is sent to which model endpoint. Local persistence, permission for cloud use of memory and tool permissions do different jobs; one "local" switch cannot describe them.

Which version this describes

The text is based on the released v0.1.63, published at 22:26:31 Beijing time (UTC+8) on 27 October 2026, and was checked as of 27 October 2026. Code links are pinned to the release commit 9bbb6ad653b4. Later development, historical features and future engineering directions are marked as such.

How to read this

For the product and its path: start here, then independent development and how it got here. For the implementation: go from architecture into the runtime, data, tools and security. To judge maturity: read testing and results and engineering directions. For specific changes: use the full version appendix.

Where it starts in code: Arslan's single entry point; task and memory-use context; model access; v0.1.63 release notes.

02 / Independent and open sourceOne person, with AI, turning an app into a system

Arslan is a project I built on my own with AI-assisted development — what is often called vibe coding. From the first prototype to every later version, I have driven it on my own.

"Independent" here means the author drives the project as a whole, with AI as a collaborator in the implementation. Arslan also stands on open-source frameworks, system APIs, external models and third-party components. How it was built, who is responsible for it and where its components come from are separate questions; what follows is about the design and implementation that actually shipped.

From generating code to maintaining a usable product

Code that runs is a small part of a product. A desktop app also has to settle how its service starts, how the window connects, how an old database is upgraded, how things are cleaned up when a process is killed, who holds system permissions, and whether the installer works on a machine with no development environment. An agent adds more uncertainty: models time out, tool arguments get truncated, pages are read incompletely, and a run may need the user's approval halfway through.

These problems pushed Arslan's iteration in four directions: explicit contracts for inputs and state, observable tool results to check what was actually done, permission boundaries around real side effects, and tests plus release acceptance to check that a change holds. AI assistance speeds up implementation and debugging, but the delivered product rests on these constraints together.

Willing to delete early assumptions

Early on, capabilities were organised around expert personas; later it converged on a single Arslan. Tools and methods could be reused, while the user's entry point and the main execution path were redesigned around real problems. 0.1.63 finished the job in code: the expert-era code no reply could reach any more (creating experts, their templates and engine) was deleted, and Arslan's product code ended 12,514 lines smaller than in 0.1.62.

Keeping evidence of what did not improve

Adding a review layer, learned practices or parallelism does not automatically make tasks go better. The project recorded a failed review layer, research tasks that lagged behind, and learning that did not speed things up — each a concrete direction for the next round. 0.1.63 removed the research review, the evolution and curation machinery and the conversation menu's "Distill" rather than keep them running.

What is Arslan's work, and what comes from dependencies

PartRole in the project
App and runtimeArslan organises the interface, model adapters, the single loop, task state, memory, tool permissions and desktop delivery into an installable personal app.
FrameworksReact, Tauri, FastAPI, SQLAlchemy, SQLite and others provide the general interface, host, service and persistence layers.
ModelsModels are called through endpoints the user configures. Arslan provides the app and the execution path; this project did not train a new foundation model.
Desktop automationHands uses agent-desktop (Apache-2.0) at upstream v0.9.4 plus four commits, built from a fork that vendors its dependencies and carries one change by Arslan (an app whose process is gone is left out of the app list), offered upstream; Arslan implements the separate helper, identity checks and permission policy around it.
Terminal rulesArslan's own module for reading shell commands, and the hard floor of its terminal rules, are adapted from MIT-licensed code from Hermes Agent; the layers of rules on top (forbidden, this time only, sandbox) and the rules for outward actions and credentials are Arslan's own. Until 0.1.62 a vendored copy of Hermes code shipped; 0.1.63 replaced it with Arslan's adapted module and removed the copy.

The project is open source under Apache-2.0. Licences and attribution for third-party components are in THIRD_PARTY_NOTICES.md, which also ships inside the app; it credits Hermes Agent (MIT), from which Arslan's reading of shell commands is adapted. Being open lets anyone inspect the implementation, its limits and how it evolved, and makes "why it was done this way" part of the documentation.

How the author works is the author's own statement. Implementation and attribution: project licence; third-party attribution; architecture decision for the runtime core; the single-loop rewrite.

03 / Tech stackHow interface, host, execution and data divide the work

Arslan's stack is organised around desktop delivery and the responsibilities of a personal agent. The exact versions below come from the v0.1.63 lock files: they are what that source resolves to, not tools a user must install, and not the product's release version.

LayerTechnologyWhat it does, and why
InterfaceReact 19.2.7
TypeScript 5.8.3
Vite 6.4.3
Component-based chat, task cards, approval cards, settings and memory pages; types help maintain cross-page state and event protocols; Vite handles development and builds.
UI state and stylingZustand 5.0.14
Tailwind CSS 4.3.1
i18next 26.3.1
Shared UI state, styling and resources for six languages. ECharts renders the charts produced in chat by the render_chart tool and run comparisons; D3 draws the memory graph. Neither is a backend inference framework.
Desktop hostTauri 2.11.5 / Rust
System WebView / objc2
Windows, tray, launch, quit, the Island, updates and macOS APIs. The Rust project declares a minimum of 1.77.2, edition 2021 — not the compiler version actually used to build.
Local servicePython ≥ 3.11
FastAPI 0.141.1
Uvicorn 0.51.0
APIs, WebSocket, the agent loop, async I/O, tool execution and scheduling. Python makes the model and document-processing ecosystem easy to reuse.
Data modelPydantic 2.13.4
SQLAlchemy 2.0.51
aiosqlite 0.22.1 / SQLite
Validation of inputs and task contracts, async database access, local persistence. SQLite keeps deployment cheap for a personal desktop product; it is not a multi-tenant distributed data platform.
Retrieval and memorySQLite FTS5
Embedding / cosine / RRF
Optional fastembed 0.8.0
Full-text and semantic retrieval complement each other; without embeddings, keyword search still works. Personal memory additionally has scope, sensitivity and use permissions.
Model transporthttpx 0.28.1
OpenAI-compatible / Anthropic / Gemini adapters
One interface for model calls that keeps each provider's tool-call, reasoning-continuation and usage semantics. Since 0.1.63 answers stream as the model writes them, for OpenAI-compatible providers, Claude and Gemini; a parameter a provider stops accepting is dropped and remembered per model. Capabilities still depend on the specific model and endpoint.
ExtensionsMCP Python SDK 1.28.1
SKILL.md / arslan.plugin.json
MCP provides a tool protocol, Skills provide methods, and a plugin manifest describes how capabilities combine. These are three different things. Enabled MCP servers start in the background, each with one state the model and the Capabilities page both read; since 0.1.63 MCP tools and rarely used tools are found on demand instead of being sent with every request.
Attachments and local OCRpypdf / pypdfium2
python-docx / python-pptx / Pillow
macOS Vision + PyObjC
Read document content and text in images. OCR reads characters; it does not understand charts or guarantee a whole document was read, and partial reads and language limits must be disclosed.
Native Mac appsArslan Hands (Rust helper)
agent-desktop (v0.9.4 + 4 upstream commits, pinned fork)
Accessibility / Unix socket
Reads the accessibility tree and, since 0.1.60, a picture of the one window it works in, and acts according to rules. agent-desktop is an Apache-2.0 dependency; the fork carries one change by Arslan, and the bundled NOTICE says so.
Cross-deviceMac Swift Bridge (this repo)
CloudKit / CryptoKit / Keychain
The iPhone side's SwiftUI app lives in a separate iOS project, not this repo
Encrypted messages, approvals and results between Mac and iPhone; the mobile app's engineering progress and its App Store status are stated separately.
Packaging and qualityuv.lock / npm lock / Cargo.lock
PyInstaller 6.21.0
pytest / Vitest / Rust / Swift tests
GitHub Actions
Reproducible dependencies, a frozen local service, cross-language tests, platform checks, signing and notarisation, and acceptance of the real installer. The desktop installer is the delivery path; the Docker deployment path was removed in 0.1.63.

Why the Python core stays

Python handles model adapters, document processing, the agent loop and task services, where an existing ecosystem and the current implementation can be reused directly; Tauri and Rust handle windows, process lifecycle and native integration. A public architecture decision records the choice to keep this core. Moving it to another language would change tools, data migration, packaging and test boundaries all at once — "more native" alone is not a measure of the gain.

The structure is layered logically but deployed mainly as one local service. Internal Python modules are not separate remote microservices, and nothing needs Redis, Celery, Docker or Kubernetes to run. Hands, Bridge, MCP servers and terminal subprocesses form process boundaries where they need separate permissions or a separate runtime.

Locked dependencies: frontend lock file; desktop lock file; Python lock file. Design and attribution: runtime-core decision; third-party attribution.

04 / How it got hereFrom generating experts to one Arslan that keeps working

Arslan has been developed continuously by its author alone, with AI-assisted vibe coding. Its history includes new capabilities and withdrawn designs: some features needed their missing conditions filled in, some complexity needed deleting. Understanding those changes explains today's architecture better than listing every historical feature side by side.

A timeline with stated limits of evidence. Early dates come from preserved Git commits; release dates come from public Releases; all are in Beijing time (UTC+8). The first commit, open-source preparation, the repository going public and the first release are different moments. The records cannot establish exactly when the repository became public; the current public repository was created on 26 July 2026.

June: first, a loop that generates agents through conversation

The earliest preserved commit, on 7 June 2026, starts with data models. The direction then was a Meta-Agent: let non-technical users create domain experts by talking, and have those experts do the work. Model adapters, a tool registry, a requirements-dialogue state machine, templates, a Spawn Manager and a CLI followed. On 8 June, FastAPI, an async database, authentication, WebSocket and a React interface were connected, giving the prototype an interactive entry point that could save its state and resume connections.

Once tools really ran, the system needed to know "what was executed" and needed material to ground answers in. On 23 June, Runs, Steps and Evaluations arrived, together with document chunking and SQLite FTS5; on 27 June, MCP's stdio and HTTP transports were connected. These changes moved the project from generating an expert prompt to managing a whole run: call the model, run tools, save the process, retrieve material, and hand the result back.

earliest data models; Runs data model; FTS5 knowledge index; MCP HTTP transport.

July: memory and delivery become engineering problems

On 5 July, the Second Brain gained embeddings and scope-filtered hybrid retrieval, combining full-text search, vector similarity and RRF rank fusion. Provenance, validity, supersession and retraction, curation proposals and usage records followed. Memory became more than a chat summary: whether material is relevant, still valid and in scope all decide whether it may enter the model's context.

Putting more history into context brings new trade-offs: retrieval that is too broad pulls in unrelated material, old facts can override new ones, and external text can influence long-term behaviour. So July's work gave facts provenance and timing, separated automatic appends from edits that need review and from sensitive content, and sent curation results to a proposal inbox. Later memory work followed these constraints; letting the user see, correct and withdraw what is remembered became a clearer engineering goal than "remember more".

This stage also began to address long-term maintenance. On 14 July, database migrations moved to a home-grown versioned runner, dropping the Alembic dependency. On 10 July, the licence changed to Apache-2.0 and NOTICE and SECURITY.md were added; on 25 July an explicit open-source preparation commit appeared; and on 27 July v0.1.0 shipped as the first desktop installer, with the Tauri shell and a frozen Python backend delivered together, and signing, notarisation, updates and first-install checks part of the release path.

Real installs soon exposed gaps that development testing could not see: the v0.1.0–0.1.6 installers lacked WebSocket runtime dependencies, so the health endpoint was fine but chat could not connect. 0.1.7 fixed the dependency layer and PyInstaller's hidden imports, and added a real handshake to installer acceptance. That failure is why "the backend started" has to be checked further as "the user's whole path works".

hybrid retrieval; versioned migrations; open-source preparation; installer WebSocket post-mortem.

August: capabilities that connect, and that users can understand

0.1.20 distinguished credentials as not configured, decryptable or not decryptable, and stabilised the salt and key migration; search gained sources and error explanations, and models began to divide work by task. MCP OAuth, OpenRouter sign-in, first-run onboarding, extension manifests, local workspace reading and writing, scheduled tasks and SSH followed. With more extensions, the system also had to say precisely whether the current connection can call tools, whether equipment is in effect, and which action needs permission.

Local execution was layered too. Reading and writing the workspace were separated, with writes needing the matching permission; scheduled tasks have their own scheduling permission; SSH can remember a machine, but each remote execution still needs confirmation. The tool list now describes both capability and conditions of use. For the user, "connected" and "allowed to run this time" are different states, and both the interface and the model's context have to express that.

0.1.23's turn journal let an answer still being generated reconnect after switching conversations; the fix that let the installed app find npx showed again that a desktop app gets a different environment from a terminal. August's thread was less about connecting more services than about turning connections, states and permissions into operations users can understand.

September: from a single answer to traceable work

0.1.38 concentrated on reliability: a default-deny Python sandbox, a separate compute runtime, persistent Runs, artifacts, checkpoints and a shared execution budget. The 0.1.40 previews and stabilisation advanced project memory, document sources, task continuation and pre-migration backups, while keeping known failures in cross-language, multi-document comparison on record. A clickable source shows that material can be found; it does not prove the model's conclusion is right.

Somewhat earlier, from 31 August to 3 September, voice went from read-aloud (0.1.32) and push-to-talk (0.1.36) to half-duplex conversation (0.1.37), with fixes to microphone permissions and language selection. Meanwhile, 0.1.40's research acceptance did not rewrite the cross-language comparison failure as a success, and 0.1.41's source review stayed off by default after a disappointing calibration (0.1.63 later removed that research review). These records bound what "the system checked it" means, and let the next improvements centre on real failures.

0.1.41 made closing the window leave Arslan working from the menu bar; 0.1.42 moved long tasks to the background so the user can keep chatting; 0.1.43 separated a soft budget limit from a hard stop — at the soft limit it wraps up with what it has, so research does not stop halfway without a deliverable. Tasks gradually gained a lifecycle independent of the interface, but that does not mean any external action can be safely replayed automatically after a crash.

Late September into October: converging on one execution loop

The early design treated experts as long-lived roles, and both user and system had to deal with choosing, inviting and routing. 0.1.44 stopped creating and dispatching experts, and old experts' methods can be converted into Skills; 0.1.46 stopped background expert evolution, which no longer had any consumer; 0.1.48 removed the front router, so every message goes straight into the agent loop. The release notes point out that some fixed "I can't do that" answers came from the routing decision rather than from real capability. This convergence put tool choice back inside the execution process that actually has the tools and the state.

After that, improvements concentrated on the shared execution layer: 0.1.49 completed the native tool protocol and truncation and failure recovery; 0.1.50 added the current working state, plans and alternative routes; 0.1.51 added the command sandbox and the Island; 0.1.52 brought user memory and learned practices into later work. "Learning" here means saving and using manageable practices, not updating a foundation model's weights.

0.1.53 goes further: a separate Hands helper reads accessibility trees and operates Mac apps, and the Mac side of the iPhone Bridge is included. 0.1.54 readies that Mac side for the iPhone app (Touch ID approvals, one entry per phone, clean-up when a phone is removed) and closes four gaps in the terminal gate, each of them asking more, not less. 0.1.55 gives everything that asks one look and one queue: cards can be answered from the Inbox and, unless the step is risky, from the Island. 0.1.56 adds projects in two layers: a board for all of them, and levels with checkpoints that Arslan ticks only from evidence it can name. 0.1.57 lets capabilities grow: when a step needs something Arslan cannot do, it finds a server or skill, asks, installs it pinned and sandboxed, tests it and retries. 0.1.58 makes replies and files readable: a quiet line of what was done, files back as cards, a reader, and any model per conversation. 0.1.59 keeps the chat alive through long answers, answers in the user's language and counts finished background work as done. 0.1.60 brings Hands v2, which can look at the one window it works in, borrow the front for a moment and, only with a yes, take over the screen. 0.1.61 brings one approval card to the chat window, the Inbox, the Island and the iPhone, MCP servers that start themselves, and a fix for Claude models that refuse the temperature setting. 0.1.62 lets long terminal commands run to their own limit, narrows "needs your check" to actions that may really have left a trace, reshapes MCP tool descriptions so one provider's refusal cannot break every chat, and stops file writes from following a link out of the working folder. 0.1.63 lets you add a message while a reply runs, streams answers as they are written, runs up to four reads at once, saves a checkpoint so a turn can be undone, sends rarely used tools only on demand, adds a dollar limit per task (on by default), and asks before every new command while the judgement of harmless commands is completed later. It also deleted code: the expert-era code no reply could reach, the evolution and curation machinery, the research review, "Distill", the Docker deployment path and the vendored Hermes copy. The old expert orchestration is history, and accessibility-based operation cannot be extrapolated to "every app is reliably controllable". The current version still constrains what the model can actually do through visible state, explicit permissions and results you can check.

reliability foundations; scope of the stable research assistant; background work; the single agent loop; Hands and Bridge.

05 / ArchitectureWho is responsible for interface, execution and permissions

In one sentence: React is the workbench you see, Tauri is the shell that connects to macOS, Python is the engine that coordinates the work, the model supplies judgement, tools produce the real actions, and the database and disk keep the process and its results. These parts make up one desktop product, but they are not one process.

Arslan v0.1.63 architectureReact connects to the Python service over HTTP and WebSocket; Tauri manages the desktop and the service's lifecycle. The single agent loop uses models, state and tools; tools reach files and the separate Hands helper. Bridge connects to the Python service through its own local control channel, then exchanges messages with the in-development iPhone client through CloudKit. The user's Mac · release v0.1.63 React 19 + TypeScript workbenchChat / Activity / Brain / tools and settings Tauri 2 + Rust desktop shellWindows / processes / Island / updates Local HTTP + WebSocket Launch / port handshake Python sidecar · FastAPI / UvicornLocal auth, API, message events, conversation and background-job lifecycle Single agent loop · tool_loop.run_nativeContext + memory + Skills → model call → validate and authorise → tool → result backShared by foreground and background; plans, budgets, cancel, wrap-up and trajectory throughout adapters User-configured model endpointOpenAI-compatible / Anthropic / GeminiCloud API, or a local model if configuredA local model can also live inside the Mac boundary State and memorySQLite / FTS5 / vector retrievalTasks, Runs, Steps, preferences, practices, receipts Tool registry and executionFiles / terminal / search / browser / MCPApp actions / Apple Events / read-only workers file I/O Unix socket Disk and workspaceDeliverables / output / backups / undoReads and writes still pass each tool's checks Arslan HandsSeparate native helperHolds Accessibility Arslan BridgeHTTP + /ws/bridgeSwift · shell-supervised Apple CloudKitSame Apple ID / private ciphertext mailboxAsync delivery; keeps needed routing metadata iPhone clientSwiftUI / CryptoKit · ask, approve, see resultsIn development and review; not yet on the storeSeparate mobile app, connected via CloudKit Arrows show the main call or control direction; responses flow back. Solid lines are this release's code paths; the dashed line marks the mobile development / acceptance boundary.The diagram shows logical relationships; it does not mean every module is always resident, called every turn, or accepted in every real scenario.

Figure 1 · Current architecture. As text: workbench ↔ local service → single agent loop ↔ model; execution loop → tools → system / files; state in SQLite and on disk. The iPhone talks to the Mac Bridge through CloudKit, not to a public API on the Mac.

Why these layers

BoundaryWhat it buysWhat it costs
React ↔ local serviceChat, settings, graph and other UI are separated from execution logic; answers stream as the model writes them, and tool events show liveA protocol, reconnection, streaming state and authentication to maintain; an open page does not mean a task is still alive
Tauri ↔ Python sidecarKeeps Python's model and document ecosystem while giving a normal Mac install experienceMulti-language packaging, dynamic dependencies, port discovery, process exit and signing all need handling
Model ↔ tool executorThe model proposes actions; deterministic code validates, authorises, executes and records themMalformed arguments, unknown tools, truncated responses, repeated failures and cancellation must be handled. Tools used rarely, and MCP tools, are found on demand rather than sent with every request; an approval card is shown where a step needs one
Separate Hands helperA separate process holds the Accessibility permission, with app-level and action-level rulesCode signing, process identity, system permissions and app differences become extra maintenance
Mac ↔ iPhoneWhile the Mac is on, online and awake, you can make requests, see results and handle supported approvals away from itWhen the Mac is unavailable, messages wait in iCloud; retries, reordering, recovery and consistent final states must be handled

How one launch wires these processes together

  1. Tauri starts the frozen Python sidecar and shows a launch screen until the service is ready.
  2. The sidecar prefers local port 47317, falls back to an ephemeral port if it is taken, and reports the port back on standard output with an ARSLAN_PORT handshake.
  3. Once the shell has the port, the system WebView loads the React page served by the local service. Preferring a fixed port keeps the WebKit origin and local UI storage stable; the server-side conversation directory lets history be found again after a port change.
  4. The interface manages resources over HTTP and receives text streams, tool status, approvals and task progress over WebSocket. Local connections still need a token and an origin check; the desktop app's server always requires its token, accepts no other website as a caller and ignores settings inherited from the environment that launched it.
  5. The shell keeps the standard-input pipe as a lifeline; when the parent exits and the pipe reaches EOF, the sidecar can clean up and exit. The output pipe must also be drained, so nothing blocks or is left as an orphan after the connection drops.

Together these details keep the window, service and database lifecycles consistent. A successful HTTP health check alone does not prove that WebSocket, attachments, static assets and the native helpers work; release acceptance checks each of them separately.

Launch details: frozen service entry and port policy; sidecar handshake and pipe management; WebSocket endpoint.

Old and new architecture: reuse what works, retire expert dispatch

History

Multiple experts / Meta-Agent

User → front router → choose or create a Spawn → expert runs → summarise; expert prompts could go through held-out evaluation and a human Promote. The product required users to understand expert identity, equipment, the roster and evolution.

0.1.63 main path

One Arslan / single loop

User → one agent loop → load Skills and call tools as needed → result; ordinary facts are extracted after the answer. Old experts can be converted into Skills and their history is kept, and a task started by an old expert is continued by Arslan when resumed. Expert routing is no longer the main execution path, and since 0.1.63 the expert-era code no reply could reach, the evolution and curation machinery and the research review are deleted.

"Single loop" does not mean "everything is serial" or "never delegates". The code still has constrained read-only workers; they are not the early expert product with its own personas, equipment, evolution and routing.

Code entry points: desktop shell and sidecar lifecycle; single entry point; execution loop; decision to keep the native Python core; Bridge process supervision; local Bridge control channel; cross-device protocol.

06 / Execution runtimeGiving every request a process, limits and a result

Arslan's execution core turns what you want into a series of observable actions: prepare the context, call the model, validate tool requests, execute what is allowed, read the real results, then decide whether to continue or deliver. Since v0.1.60 the default entry point is the single loop; a message goes straight into answering, and long-term facts are extracted after the answer. The main flow does not start with a model routing call, and the expert-era code that no reply could reach any more (creating experts, their templates and engine) was removed in v0.1.63.

Set up the taskBind conversation, workspace and permissions
Prepare inputHistory, memory, material and available tools
Model decidesRequest a tool, or write the answer
Real executionValidate, authorise, record, return the result
Check and deliverCheck the contract, evidence and deliverables

Source: message entry and context assembly; trusted task context; single tool loop.

How one task runs, including approval 1. Receive the requestText / attachments / speech as text 2. Assemble contextHistory, memory, workspace, Skills 3. Model picks next stepAnswer, or request a tool call 4. Validate tool and argsSchema / availability / completenessA truncated call never runs 5. Policyrun / ask / forbid needs OK Show the exact actionTarget, risk, reason, allow or denyThe card has its own time limit once allowed allowed: run directly 6. Run the real toolFiles / commands / browser / Mac appsSide effects stay inside its boundary 7. Results and stateExit code / output / files / trajectoryWhy it failed + other routes tool result back to model direct answer budget wrap-up / stop 8. Check answer and files, then deliver honestlyEvidence: real tool results, file references and the task contractSays what is done, what is not and how to continue; failure is never shown as success forbid denied / timed out Action not executedSays why: forbidden, denied, cancelledFind another route, or say it isn't done at wrap-up After answering: preferences and practices saved as permitted; the rest goes to review

Figure 2 · The execution loop. As text: input → context → model → argument validation → authorisation → tool execution → result back, then loop again; the model can also answer directly. Answers and files are checked before delivery, or the result honestly says what is not done; forbid and deny cannot be overridden by the model.

The model proposes the next step; the runtime sets the conditions

To the model, tools are a set of names, purposes and argument schemas. When it returns structured tool_calls, the runtime parses the JSON, validates the arguments, then re-checks that the tool is still available, that the budget allows it and whether the user must authorise it. Built-in tools map to executor functions through the registry; tools prefixed mcp_ go to MCP, and the model is offered only MCP servers that are ready right now. A tool being in the registry does not mean it is offered in every turn: since v0.1.63 a request carries only the tools in regular use plus two finders, find_tools and use_tool; rarely used tools and MCP tools are found and called on demand, under the same approvals and records as a direct call. The list a conversation has used on demand grows at most to 12 and is kept in memory only.

One key boundary is response completeness: if the provider truncated a tool call at its output limit, none of that batch runs, even if one call looks complete. Model text that comes with tool calls is narration of the process; it cannot stand in for the final deliverable. Checkpoints are recorded before and after execution, and real tool results return to the model tagged with the call they answer; content from web pages, files or external tools is still handled as data.

So "I saved the file" has to correspond to an actual save result and a reference to the file; a sentence of promise does not turn into something that happened. A failed tool becomes input to the next round too; the loop can change route, but under the same permissions and budget.

Take "read this folder and make a list": the folder path enters the task's scope as input; the model asks to read it, the tool returns the real file information, and only then is there a basis for the list. If you then ask to move or delete files, the runtime decides authorisation for that operation separately. The delivery should point at the file actually produced and say how it was checked. The file listing, the action receipts and the file reference each serve as different evidence.

Since v0.1.63 the answer appears as it is written (on the endpoints listed below), and checks that used to hold an answer back add a correction after it when needed; if a stream fails halfway, Arslan keeps what you saw and says the reply was interrupted instead of starting again. When the model asks for several reads in a row (web pages, searches, MCP tools their server marks read-only), up to four run at the same time; each result stays paired with the call that asked for it, and any call that is not a read runs alone in its place.

Source: reads that run together; streaming and the tool loop.

One entry point, different model protocols underneath

LLMAdapter provides one calling interface; each provider handles request formats, streaming events, tool calls and usage. Providers send HTTP requests with httpx; an OpenAI-compatible endpoint can also point at a local model server. With a cloud model, the request context goes to the provider the user configured.

Protocol pathWhat v0.1.63 actually does
OpenAI-compatibleSupports native trajectory: assistant tool calls, call IDs and tool results are rendered as native message history; certain protocol rejections can trigger a limited fallback. The reply streams as it is written on DeepSeek, OpenAI and OpenRouter endpoints, where streamed tool calls are known to be complete; other endpoints answer whole.
AnthropicSince v0.1.63 also uses the native trajectory. On Anthropic's own host (or a listed first-party proxy) the reply streams as it is written and the API is asked to clear old tool results itself; another Anthropic-protocol endpoint answers whole.
GeminiKeeps its own representation of tool calls and history, and maintains the provider continuation data it needs; since v0.1.63 it also uses the native trajectory, and streams only on Google's own host.

The trajectory is the loop's structured record of which call produced which result. It keeps internal records uniform while letting each provider use its own protocol, instead of flattening tool interactions into plain text that is hard to check. If an endpoint rejects the native history, the turn switches to the legacy rendering for the rest of that turn.

Two repairs sit at this layer. Since 0.1.61, when a provider refuses an optional request setting (for example temperature on newer Claude models), Arslan resends without it, or under the name the error asks for, and remembers that for the model until restart. Since 0.1.62, MCP tool descriptions are reshaped into a form Anthropic, OpenAI-compatible providers and Gemini accept; if a provider still refuses one tool, that tool is left out for the rest of the reply (a second refusal, or a refusal naming no tool, leaves out all MCP tools for that reply). Built-in tools are sent as written.

Source: model facade; trajectory data structure; provider capability contract; resending without a refused setting; MCP tool description reshaping; tools on demand; execution-layer change notes.

Temporary helpers and background jobs have different jobs

delegate_work splits independent research across temporary workers: at most 4 per batch, at most 2 running at once. Workers currently get only web_search and web_extract; they cannot write files or personal memory, and cannot delegate further. The main executor takes the material back and is responsible for checking and combining it. Parallel research stays useful while responsibility for the work stays with the original task.

A background job is for a longer, complete task: it has its own context and budget, reuses the tool loop, deliverable checks and repair flow, reports progress back to the conversation through a task card, and by default at most 3 run at once. The network connection, the chat window and background execution each have their own lifecycle; switching conversations does not cancel a task. If the Mac sleeps or the process exits, background work is not handed to the cloud to continue.

Wrap up first, before the hard limit

The budget counts model requests, tool calls, cumulative tokens, running time and, since v0.1.63, dollars together. A chat reply and a background task now get the same limits: the tier chosen in Settings, standard by default. At the soft limit, the loop should stop widening its research and organise what it has; the hard limit stops runaway consumption. Nested runs and retries count toward the task budget, and repeated calls that make no progress are detected separately, so the same failing route is not tried over and over.

Tier (chat reply and background task)Model requestsTool callsCumulative tokensRunning time
Lean6040300,000900 s
Standard (default)12080600,0001,800 s
Ample2401601,200,0003,600 s

These are the soft defaults; each hard limit is 1.5 times the soft one. Per-response output limits and deliverable size limits are counted separately. Token usage is charged when a response returns, so these thresholds are scheduling boundaries, not exact cost caps or promises about completion time. Time spent waiting for an approval card is not counted as running time. On top of the tiers there is a dollar limit per task, on by default at $2 (Settings › Background › Dollar limit per task): the run starts wrapping up at two thirds of it and stops at it. Only responses from models with a known price count, so a local model or one missing from the price table adds nothing and the token limits still hold; the check happens before the next request, so a run can overshoot by one response. A resumed task inherits the counts, running time and dollars already used; time spent stopped is not counted as running time, and the budget is not reset.

Long work stays in the chat: Arslan no longer pushes it into the background by itself; a background task is the same work done elsewhere, when you or the model choose it. After ten minutes a reply tells you it is still working. A terminal command runs for as long as it asked (2 minutes by default, at most 10), never past the time the reply has left; if a time limit stops it, Arslan still sees the output so far and is told so, and a command that floods its output keeps its beginning and end. Every new command shows a card first by default (v0.1.63), and a scheduled task does not run commands in this version: that step is skipped and listed in the Inbox. An unanswered card expires after 30 minutes, which Arslan is told is not a refusal.

Source: budget rules; command time limit and output cap; approval cards and their expiry; temporary workers; background jobs.

Tasks, attempts and acceptance are recorded separately

TaskSpec holds the task contract — instructions, inputs, scope, acceptance checks and budget — with a revision number. A task can go through several attempts; an attempt is one concrete run. A changed contract, an old result returning after cancellation, and a new attempt after resuming must never write over each other's state. Persistent updates check both the attempt identity and the row version, so a stale run cannot overwrite newer state.

StateMeaning
queued / runningWaiting to start / running.
waiting_userNeeds the user to act or explicitly continue; the state itself does not mean the work is done.
verifyingChecking evidence and deliverables against the task contract.
succeeded / failed / cancelledPassed acceptance / failed / cancelled.

Acceptance results distinguish passed, failed, not run, not applicable and similar cases. A verdict needs matching evidence; agreed checks cannot be skipped, and not everything can rest on the model's self-assessment. File existence, which task a file belongs to and its digest can be checked deterministically; only subjective quality is suited to model or human review. While any action is still prepared, started or of unknown effect, the repository layer also stops the task from becoming a success.

This is also why a revised task must keep its identity: results produced for an old requirement do not automatically prove the new one is met. Acceptance must match the same contract revision, the same valid attempt and evidence owned by that task. Key checks cannot casually be marked "not applicable"; only non-key items that fit the contract's rules can be. When evidence is missing, the result stays unverified or incomplete until it is checked.

Resuming continues from existing evidence

Checkpoints keep progress, budget used, evidence and file references; run events let the interface catch up after reconnecting. The in-process run registry holds the actual async tasks and their cancel handles and dispatches events to connected windows; on reconnect it takes a snapshot and attaches the event stream in one synchronous step, to avoid dropped or duplicated frames at the handover.

After a process restart, unfinished runs are recognised as interrupted and continue only when the user explicitly resumes them. An external action that was sent but never acknowledged has an unknown effect; it must be checked before anything is resubmitted. Restoring a backup also revokes old action grants and marks the affected actions as uncertain. A checkpoint answers "where can we safely continue from"; it cannot undo external results that already happened.

A separate kind of checkpoint serves "Undo this turn" (v0.1.63). The first time a turn writes a file or runs a command, Arslan saves the working folder as it was, once. Undo puts the files back and removes that turn and every later one from the conversation, together with the facts they taught; summaries are kept and message ids are never reused. The folder is saved only when it has at most 20,000 files and 5 GB, there is enough free disk and it is on the same disk as Arslan's data. Rebuildable folders (Python environments, node_modules, build output, __pycache__) and secret-shaped files are left out and never put back; the undo box says what was not saved. Files changed after the turn are kept and listed, and undo is refused while a run is still changing the folder.

Stopping also has two layers: the run registry cancels async work that is waiting or running, and persistent state stops late results from being committed. For a request an external system already accepted, cancelling the local wait does not prove the other side did nothing. So the action table distinguishes prepared, started, confirmed, failed, uncertain and cancelled, and resuming deals with those states before deciding which steps may be retried.

Source: task and acceptance contracts; persistent state and concurrency checks; deliverable validation; execution and explicit resume; separating connections from runs; backup-restore boundary; folder checkpoints for undo; undoing a turn.

A turn that keeps the window informed

Since 0.1.59, while a turn runs the server sends the window a "working" event at least every 15 seconds, even when the model spends minutes writing one long file, so "Interrupted" now means the connection itself was lost. Since 0.1.63 a message sent while Arslan is still answering reaches that reply at its next step instead of waiting for it to end: the steps not started yet are skipped, the reply reads the message first and the window says it was added. A reply already writing its final answer has no step left, so the message runs right after it. While an approval card waits, a message answers it No, with the message as the note, and runs next. The reply language follows the user's latest message: paths, URLs and code are ignored when judging it, and a message with no words follows the interface language. An approval asked while the window's connection has closed no longer fails the turn; the card waits in the Inbox, on the Island or in the reconnected window.

Source: working events, the queue and cards after a reconnect; a message added to a running reply; reply language.

Projects: a board, levels and checkpoints that need evidence

Since 0.1.56 a project has a plan of levels, each with a clear condition and a few checkpoints. Picking a type (game, app, website, research, writing, video, skill, trip, job or other) drafts the levels from a template without a model call; "Let Arslan refine" adapts them to the finish line with one call on the user's own model, and nothing is saved until the user creates the project. The board's columns — Idea, Shaping, Doing, Done — are derived from the current level, never dragged; Done and Dropped are set only by the user.

Arslan moves a plan only from evidence it can name, and only for the current level: a checkpoint that names files is ticked when they appear in the project folder; after the user's own turn a judge asks whether the message says something is done; a background job started from the project's hand-off ticks its checkpoint when it ends done. Small ticks are undoable; clearing a level is a proposal on the card, never a notification or an Inbox item. Only after ten right guesses in a row does Arslan ask once whether it may move levels itself. The plan rules it learns, the user's pace and the "stalled" hint stay local, and reach a model only inside a drafting request the user started.

Source: levels, board and proposals; files, what the user said, runs; level templates; plan rules, pace and stalled.

07 / Data and memoryKeeping useful context, and clear data boundaries

Arslan keeps its main state in SQLite on your Mac: conversations, run records, tasks, personal memory, model configuration and usage each have a data model. Deliverables are written to disk; the full text of very long tool output is stored separately in the app's data folder (kept for 7 days), and the model reads it in parts from the path given in the result. Closing a window therefore does not erase a task's process, and "what was said", "what was actually done" and "what may be remembered later" can be managed separately.

Data access uses async SQLAlchemy with aiosqlite. Connections enable WAL, foreign-key checks, a 5-second busy timeout and synchronous=NORMAL. These choices suit a single-user local app: fewer deployment dependencies and better behaviour when reads and writes overlap. The data folder also has a cooperative process lock, so instances that follow the same protocol do not use the same data at once.

How memory is formed, permitted and recalled SourcesYour words, corrections, tool runsWeb pages and files: external Scope and trustTemporary? Sensitive? External?Preference / practice / machine quirk Active, or for reviewAllowed ordinary items take effect nowSensitive / externally influenced: review Stored and visibleContent / revision / source / use policyView, retract or delete in Brain Filtered before next usePersonal / project scope + cloud permissionRelevance, caps, current validity Into this turn's contextAbout you / Mac notes / lessonsUse receipts and outcomes recorded A learned practice can improve the method; it can never add a permission.Deleting is more than hiding in the UI: old revisions, sources, summaries and the deletion ledger are handled so old state cannot bring it back.

Figure 3 · The memory loop. As text: source → judgement → active / proposal → stored → filtered by scope and permission → recalled → use recorded. Remembering "use EventKit" does not grant permission to Reminders.

Data objects, layered by responsibility

GroupMain objectsQuestion it answers
Conversations and runsArslanMessage, ArslanSummary, Run, RunStep, UsageLedgerWhat the user said, what was called, how much it consumed.
Durable taskscompanion_tasks, task_revisions, task_attempts, task_checkpoints, task_events, task_actionsWhich contract revision a task uses, which attempt it is on, what actions and evidence it has.
Personal memorymemory_entries, memory_revisions, memory_sourcesWhat is remembered, which revision is current, where it came from.
Use and deletion recordscontext_receipts, memory_deletions, memory_suppressionsWhich memory revision entered a context, and which deletions must keep applying.
Permissions and approvalspermission_rules, permission_grants, approval_eventsWhich standing answers ("Always") and per-task or per-conversation answers exist, and every decision that crossed a boundary, who made it and how long the card waited.
Material and practicesKnowledgeChunk, Lesson, JudgmentSearchable material, reusable practices and the results of small judgements.

The task table holds current state, the revision table the task contract, the attempt table each run, the event table an ordered event stream, and the action table real actions and their effect status. Together they support checking and recovery. Personal memory separates a stable identity, content revisions and sources, which makes editing, tracing and deletion easier; it does not reuse the whole chat as permanent memory.

Source: SQLite connection; run, material and configuration models; task models; personal context and memory models.

Working context: control this turn's input, keep the raw history

A model can only take in so much at a time, so Arslan builds the working context from a bounded rolling summary plus recent messages. Since v0.1.63 the history window is sized by the turn's model: 40% of its context window as an estimate, never below 3,000 or above 24,000 tokens (an unknown model assumes 32,000, a local model 8,192), and the summary itself is length-limited too. That is the budget for assembling history, not the size of the whole request: system instructions, tool schemas, material and results take up other space.

As a conversation grows, the latest four user turns stay verbatim (while they fit in half the budget) and the tool summaries of older turns are masked first, which costs nothing; once the history passes three quarters of its budget, older messages are folded into the summary, which keeps the sections Goal, Progress, Decisions, Files touched and Next steps. What changes from turn to turn (facts, summary, material, the reply language) rides in a reference block at the start of the turn's request, not in the system prompt, and is never stored as the user's words. Compaction changes what is assembled next time; it does not delete the full conversation from the database, and if compaction fails the original record is kept. Very long tool output is stored separately: the model gets the head and tail plus a file path and reads more in parts when needed, and the full output is cleaned up under its retention policy.

This layering lets the model see what the current task needs first, while keeping the originals for later checking. A summary is a compressed view of what exists; it cannot replace the real receipt of an external action.

Personal memory: first whether it may be used, then whether it is relevant

A memory entry has a type, scope, sensitivity level, use policy, state, confidence and version. The scope can be global or a project (with internal scopes such as domain and expert), and each entry also has an owner; use policies such as local_only further decide whether content may go into a cloud model request. Each turn's trusted context loads conversation settings from the database; ordinary user text, web pages or model output cannot rewrite these permissions.

Read conversation settingsProject, temporary chat, memory permission
Filter by boundaryScope, state, sensitivity, cloud policy
Choose contentBase memory and relevance ranking
Inject within capsControlled content enters this turn
Record a receiptEntry IDs, revisions, reason for use

"About you" and "Mac notes" get a capped base injection; everything else is chosen by relevance. Since v0.1.52, "Remember me and use it in conversations", on by default, lets ordinary cloud_allowed entries be used with a configured cloud model. Sensitive content needs a further permission; local_only never becomes cloud-eligible because the main switch is on.

ContextReceipt records which entries and revisions were used, why, and an estimated count, without copying the memory text. You can trace why a context was chosen, and the system does not have to create another copy of sensitive content to do so.

Editing a memory creates a new revision and moves the entry's current-revision pointer; source records continue to explain how that revision came about. Paused, expired, superseded and deleted states also affect whether an entry enters later contexts. Versioning helps with checking changes, but deletion erases the text that must go — so it is not an event store where history can never be deleted.

Source: history and summaries; long tool output; history size by model window; per-turn reference block; conversation permissions; scope and privacy filtering, receipts.

Retrieval: keywords and vectors can be combined

The knowledge retrieval module supports SQLite FTS5 full-text search and vector cosine-similarity search. The first finds word matches; the second uses embeddings to find semantically related passages; when both are available, their rankings are fused with RRF. RRF compares rank positions rather than mixing raw scores on different scales. Without embeddings, or without vectors, full-text search still works.

Retrieved passages enter the model's context with their sources, and the model uses them to answer the current question — that is what RAG means here. Material retrieval runs only in projects that have bound collections; with a cloud model, the conversation's own cloud-memory permission must also be on (the default "Remember me" switch does not cover material). Results enter the context as the return value of the recall tool. None of this changes the foundation model's weights or needs a separately deployed vector database. Personal-memory v2 selection mainly uses permission filtering, full-text and local relevance logic; it does not follow that every memory goes through the same vector pipeline.

Learned practices: turning corrections into reviewable rules

A Lesson records a user correction, a detour that worked after a failure, or a quirk of this machine. For example, if a command needs different arguments on this Mac, that can become a practice with its source, for similar tasks to consult. Recall takes at most 5 items, about 800 characters in all, placed in the dynamic agent status rather than the stable system prefix; recalls, follows, successes and failures are each counted.

Whether something is worth saving, or duplicates or conflicts with what exists, can be put to the judgement layer as a narrowly scoped question, with the probability and later outcome recorded. User corrections, and practices not influenced by external content, can take effect directly when settings allow and the judgement finds them valuable; anything else needing review becomes a proposal. Candidates containing credentials are dropped outright; the rule that inferred sensitive information needs review belongs to personal memory, not to practices. There are fallbacks when the judgement is missing or times out.

Since 0.1.55 a correction only counts as one if it answers something Arslan did earlier in the conversation: a first message is a request, never a correction. A correction that goes against the route that just worked in the same turn becomes a proposal waiting for the user's OK instead of taking effect at once, and a failure the user has to fix, such as macOS refusing a permission, is never learned as a reason to take another route. When Arslan saves a memory during a conversation, it says so in the thread, with Undo.

These mechanisms change what the app saves and recalls; they do not train model weights. Judgements are enabled to different extents: the memory, practice and project points run after the answer, and a registered one (turn completion) is off. No judgement decides whether a command runs: the command cards' shadow judgement was removed in v0.1.63, and only the authorisation flow decides.

Source: hybrid retrieval; extracting and recalling practices; small judgements and their modes.

Deletion has to reach summaries and old backups

When a memory is deleted, the system raises the deletion epoch, erases that entry's old revision text and sources, and clears every conversation's rolling summary and the prompts cached in run records, so old derived content cannot bring the deleted memory back. The deletion record keeps only a content fingerprint for matching (an HMAC with a local key) plus metadata such as scope, so the deleted text is not saved again in the ledger; a suppression record stops the same source, still in the chat history, from automatically recreating the memory. Deleting a memory and deleting the original conversation are different operations. "Undo this turn" is a third, narrower one: it deletes the entries whose every source is an undone turn (an entry something else also backs loses only those sources), leaves no text tombstone, and keeps summaries.

A separate deletion ledger and deletion manifest keep deletions applied on start-up and restore paths. Before an old backup is restored, the system checks the store's identity and reconciles the staged data against the deletion records before activating it, reducing the chance that an old backup resurrects deleted memories. This depends on the restore flow and the deletion records available; it cannot recall copies already exported elsewhere.

The rest of the memories in a backup do not regain all their permissions immediately either: restoring quarantines them for review, tightens use policies, clears cached context, disables all scheduled tasks, revokes per-conversation cloud and sensitive permissions, and archives active projects. Restoring data and re-approving its use are done separately, so old conversation permissions do not silently take effect in a new environment.

Migrations finish before the app starts using data

Database upgrades use an explicit migration runner: check the schema version, prepare an upgrade backup, create missing tables, run pending migrations in order, then handle key compatibility and memory v2 activation. Unknown or newer schemas are refused, so an older program never blindly modifies newer data. In v0.1.63 the explicit registry goes up to 0070; the early Alembic history is not the current mechanism. Since v0.1.60 it added 0067 (the permission tables), 0068 (one state per MCP server and each tool's hints), 0069 (message ids are never reused, so undoing a turn cannot hand a deleted id out again) and 0070 (a stored "ask only for risky commands" moves once to "ask before every command").

When memory v2 activates, the old fact and learning tables are renamed to legacy tables with read-only triggers, compatibility views map old read paths to the new structure, and the full-text index is built at the same time. Secret fields — model API keys, MCP environment variables and OAuth tokens, SSH keys and the like — use Fernet field-level encryption (PBKDF2 plus a salt unique to each install); ordinary conversation and task tables are not thereby encrypted as a whole database. Persistence, secret protection and backup-restore each have specific boundaries.

Source: memory deletion and versions; standing and per-task answers; undoing a turn; deletion ledger; deletion check before restore; backup and restore coordination; quarantine of restored data; storage start-up order; explicit migrations; permission tables; memory structure activation; secret field protection.

08 / Tools, Skills and extensionsReusable capabilities, with limits on execution

Arslan keeps "what it can do" separate from "knowing how to do it". Tools perform real actions, Skills hold methods and references, MCP connects external capabilities, and a plugin manifest combines these resources. The model receives the set of capabilities available this turn; a tool existing in the source registry does not mean it is on by default or allowed to run.

How the four concepts fit together

PartWhat it providesWhat it cannot replace
ToolAn action with a name, an argument contract and an executor — searching, reading a file, running a commandA tool description cannot replace argument validation, permission decisions or the actual return value
SkillA working method in SKILL.md, plus optional scripts and reference materialIt gets no system permissions by itself and is not a separately trained model
MCPA protocol for discovering and calling external tools over stdio or HTTPA common interface does not make a server trustworthy; the app still controls connection, exposure and authorisation
Plugin manifestarslan.plugin.json declares how MCP servers start, secret slots and related SkillsInstalling and parsing a manifest does not immediately run any server in it

From declaration to execution: the checks in between

DiscoverBuilt-in registry, configured MCP, Skill index
Filter and offerThis turn's schemas, by context, config and permissions
Parse the requestCheck the tool name, JSON arguments, completeness
Execute and returnCall the executor once authorised; record results and failures

Built-in tools are handled by registered executors; calls prefixed mcp_ go to the MCP proxy. Tool results return to the single loop, where the model can fill in arguments, choose another method or finish. Even after the model has read a Skill, files, commands and app operations still pass through their own permission boundaries.

Since 0.1.63 a request no longer carries every tool. It carries a resident set of everyday tools (web search and page reading, file read, write and edit, the terminal, planning, background work, memory, read_skill, the capabilities list, and the desktop tools of Hands) plus two finders: find_tools ranks the tools this turn may call but was not shown, and use_tool calls one under its real name, so the arguments are checked against that tool's own parameters and the same approval, trace and receipts apply as for a direct call. MCP tools, attach_file and the browser tools are reached this way. A tool used on demand joins that conversation's list from the next turn (at most 12 per conversation, kept in memory, so after a restart the model searches again). Text from an MCP server that find_tools hands back is wrapped as outside data, never as instructions.

When the model asks for several reads in a row, up to four run at the same time: web search and page reads, read_skill, file reads, listings and the like, and MCP tools whose server marks them read-only (one per server in a group). Each call still passes every gate in the reply's order and each result stays paired with the call that asked for it; anything that is not a read runs alone, in its place.

Skills are read on demand: an index tells the model which methods exist, and read_skill fetches the body from a registered SkillPack or an allowed reference path. The library of methods can grow while each turn's context stays small. Arslan ships two skills of its own, a designed HTML report and the skill creator. Since 0.1.63 the expert-era seeds, the preset MCP connector list and the model's connector-suggestion tool are gone (finding a new MCP server goes through find_capability, below), and a skill you imported under a retired name is no longer removed.

Since 0.1.55 the Capabilities page lists what Arslan can do as switches, each on, off, one step away or not possible here, with the reason. Off means the model is not offered it at all, and read_skill refuses a switched-off skill. The rows are read from the same gates that decide which tools a turn gets, so a row cannot say "on" while its tools are missing. A whats_new tool answers "which version are you, what changed" from the release notes shipped with the app, never from the model's memory.

Extensions are a new trust boundary

A local stdio MCP server usually means starting a separate process; an HTTP MCP server may connect to a remote service. Arslan manages separately whether a connection exists, whether the main assistant can use it, whether its tools are exposed, and how its credentials are stored and shown. HTTP MCP server URLs declared in a plugin manifest must use HTTPS; secret slots declare the shape of a parameter and never embed a real credential in the manifest. Configured environment values are stored encrypted and masked when shown.

Since 0.1.61 every MCP server you switched on starts in the background when Arslan opens, at most four at a time, and a chat never waits for one. Each server has one state that the Capabilities page and the model both read, and the model is offered only the servers that are ready right now; a call to any other is answered with its state and the reason, with keys and tokens masked. A first start gets 120 seconds; a failed server is tried again after 30 seconds, then less often (at most every 10 minutes).

Since 0.1.62 Arslan reshapes the parameter descriptions of MCP tools into a form Anthropic, OpenAI-compatible providers and Gemini accept before sending them; built-in tools are sent as written. If a provider still refuses one, that tool is left out for the rest of the reply and the reply goes on (if the refusal names no tool, or a second one is refused, all MCP tools are left out for that reply).

Since 0.1.63 an MCP call asks according to what the server says the tool does: a tool the server marks read-only runs without a card; one it marks destructive asks every time, this time only; any other asks, this time or for the conversation, never "Always". A call whose arguments are too long for a card to show in full is refused, never cut. These marks are the server's own statement, which Arslan does not verify. Where nobody can be asked (a scheduled run), only read-only tools run.

These constraints reduce accidental exposure and mistaken execution, but a real MCP server can still access whatever it is given. Where a tool comes from, what it needs, what data goes to a remote service and what side effects it has are all part of what an extension must explain.

Capabilities that grow: find, ask, install, scan, test

Since 0.1.57, when a step needs something Arslan cannot do, find_capability searches the official MCP Registry, GitHub and reviewed skill libraries together. propose_capability may only name a candidate this conversation was shown, and the card is built on the server from that search result, never from the model's words: what it would add, where it comes from, its license read from the project's own license file, how it would run, which folders it may use and any key it needs. Only the Arslan window that asked can approve; a background job, or a turn with nobody to ask, records the find under "Arslan found" on the Capabilities page instead.

After approval it is installed pinned and switched off: a lock with hashes, a published checksum or a commit, on Arslan's own pinned uv or Node, with no package code running during the install (wheels only, npm without install scripts). A scan looks for known dangerous patterns — hidden instructions in a skill, code that fetches and runs more code, writes to where macOS starts programs — and a high finding removes it again; the scan cannot prove anything safe, which is why the server then runs in its own sandbox (chapter 09). It must start and list at least one tool before it is switched on, and the tool loop re-reads its tools so the failed step can be retried in the same reply. Skills imported from GitHub are pinned to one commit with a checksum per file, and their text is read as outside content that cannot grant permissions.

Source: ask, install, scan, test, switch on; pinned installs and license at the source; known-pattern scan; MCP Registry, GitHub and skill libraries.

Files come back as files: the workbench reader

Since 0.1.58 a file written during a turn shows under the reply as a card, and paths in a reply open in Arslan or in the file browser. The reader in the workbench reads only where Arslan's own read tools may: the workspace, Desktop, Documents and Downloads when default read is on, active project folders and Arslan's own outputs. Symlinks are resolved first, hidden files and credential-shaped names are never listed or read, file bytes are sent as an attachment with nosniff, and HTML is shown in an iframe with an empty sandbox, so its scripts cannot run.

Since 0.1.63, when you ask for a file (a screenshot, chart, export or document), attach_file attaches it to the answer: a file card in the window, and the file itself on a paired iPhone. It takes no card of its own because it only reads where the reader may, and the same rules apply: no hidden or credential-shaped names, no link leading out of the readable folders. It also never takes anything under Arslan's own data, keychains or SSH keys, a file over 20 MB, or more files than one run can hold.

Source: attach_file; reader roots and hidden names; file bytes; sandboxed HTML.

Hands v2: one window's picture, batches, borrowing the front, taking over

Hands still reads a window as its accessibility tree. Since 0.1.60 each look can also attach a JPEG of that one window — never the whole screen — held in memory and sent only to the model; it is not written to disk, to the trace or to the interface, and when the model cannot take images, macOS Vision reads the text locally instead. It can be turned off in Settings → Hands, or kept to text for apps you list. Several steps in one app run as one batch of up to eight, menu items are chosen directly even when the app is not in front, and apps are opened in the background. Until 0.1.62 a chat reply could take at most five actions, all in one app; from 0.1.63 it has no Hands limit of its own and acts under the run's limits, as background work does. A scheduled run cannot act on the Mac at all: nobody is there to approve it.

Some things only work with the app in front, such as a pop-up menu. Hands then waits for a one-second pause in typing, borrows the front for about a second while the edges of every display glow blue, holds the keys pressed meanwhile and replays them when the user's app is back. If the app's window is on another desktop, Hands goes there under the same rules and comes back when the work ends, after 15 seconds without action or at the user's first input. For longer work a background job can ask to take over the screen for up to 30 minutes; any keyboard or mouse input pauses it until the user continues. "While I'm away" is off by default; when on, a takeover may start without a card after three minutes without input. Borrowing can be turned off, and Hands then refuses instead.

Source: one window, nothing written; borrowing the front; another desktop; takeover and the input pause; actions in a chat reply, batches and menus; Settings → Hands.

Search and reading the web

Since 0.1.63 web_search uses the search of the model this turn runs on first: Anthropic's own web search for a Claude conversation on Anthropic's own API, DeepSeek's for a DeepSeek model. A conversation on a local model never sends its queries to a provider's own search. Tavily or a self-hosted SearXNG chosen in Settings is either the only search or a backup after the model's own; with no deliberate choice, a best-effort DuckDuckGo scrape comes last. When a search service refuses Arslan, the result says the search was blocked and that nothing was searched, never "no results".

Page reads and the browser keep the outbound guard: local names, real private addresses and hosts with control characters are refused. Since 0.1.61, on a Mac whose proxy or VPN answers DNS with fake addresses (198.18.0.0/15), a name asked for over https on port 443 whose addresses all lie in that block is let through, and plain http or another port to such a name is refused. For such a name the proxy decides where the request really goes; what still holds is TLS, checked against the real name.

Source: search order and blocked searches; provider searches; outbound guard and fake-IP exception.

Why parallel research still exists

The single agent loop keeps constrained, temporary read-only workers. In v0.1.63 a batch has at most 4, and at most 2 run at once; their tools are limited to search and web extraction, they write no files or personal memory, and they cannot create more workers. The main assistant checks and combines their results.

This kind of delegation suits splitting up independent lookups. Its lifecycle differs from the early permanent expert personas: you are not asked to pick or manage a team of experts first, and messages are not forced through to another "expert". Whether to use parallelism should still depend on whether the task splits well, and on the cost of the extra calls.

Background, scheduled and heartbeat

A background job separates long execution from the current chat, reusing the same model, tools and acceptance logic. A scheduled task persists "when to start"; the heartbeat is one way of periodically working through a checklist — off by default, it checks and suggests. A scheduled trigger never turns a suggestion into an approved write or command. Since 0.1.63 every scheduled run is an ordinary Arslan turn with nobody to ask: in this version it runs no terminal command at all, whatever was allowed before, writes only in Arslan's own folder, cannot act on the Mac, and runs only MCP tools their server marks read-only; a step that needs more is refused and listed in the Inbox.

Chat replies and background tasks stop at a dollar limit per task (on by default, $2; they start wrapping up at about two thirds of it; only models with a known price count), changeable in Settings. A chat reply is no longer pushed into the background for long work: background work is the same work done elsewhere, when you or the model choose it.

Scheduling is implemented in the local service, with overlap skipping, pausing after failures and clean-up after a restart. Missed runs are not replayed one by one; if the previous run of the same task has not finished, the next one does not start on top of it. This suits a personal desktop app, and it also means its operating conditions differ from a server that is always online.

Implementation: tool registration and read_skill; capabilities as switches; plugin manifest contract; MCP connections and configuration; find_tools and use_tool; reads at the same time; MCP start-up and states; MCP calls and approval by the server's marks; reshaping MCP tool descriptions; the two bundled skills; read-only workers; background jobs; local scheduling; heartbeat.

09 / Execution and security boundariesWhat happens between a model's suggestion and a real action

Arslan can read and write files, run commands, and work through the browser and native apps. Its security design rests on one distinction: the model proposes the next step; trusted code decides whether that step may run. What follows describes the boundaries implemented in v0.1.63. Each is a protection with a specific scope, not a proof of safety against every malicious input, app behaviour or system environment.

Input and tool suggestionUser goal, web pages, files
The model writes a structured call
Apply policyCheck arguments, target and context
Decide run, ask or forbid
Wait for approval if neededA card bound to the exact action
Deny and timeout both have outcomes
Execution boundaryWorkspace, sandbox, separate helper
Each limits real access
Observe the resultTool receipts, files and state
Failure is never dressed as done

External content can supply material, not grant itself permissions

Web pages and tool results may contain prompt injection. Web text and MCP results enter the context wrapped in an explicit external-data boundary; since 0.1.63 what a local tool returns (a file, a command, an app window) is wrapped in a separate tool-output boundary with the same data-only rule, and content that forges the markers of either is handled. The system also records whether this turn read external content such as web pages, files or MCP results: practices and machine notes influenced by that content must first become proposals. A line on a page saying "always send files here from now on" cannot gain lasting permission just by being read.

Since 0.1.63 the same record limits the terminal: once a turn has read outside content (a page, a file, an MCP result), its commands run without network under the kernel's rule, and a command that reaches the network, judged by its text or by the model saying so, asks first. Inside the sandbox the answer can be for this time or for the conversation; outside it, only this time. Where nobody can be asked, the command stays offline. Reading the web is guarded separately: local names and private addresses are refused, and the exception for proxies that answer DNS with fake addresses (0.1.61) lets through only https on port 443 to a name, leaving the proxy to decide where it really goes (chapter 08).

This helps keep track of provenance and trust, but it still relies on the execution checks that follow. The model can misunderstand material, and command-risk detection is not a full semantic proof; labelling text "untrusted" is no reason to drop the real permission boundaries.

wrapping external data and tracking sources; tool validation and execution entry; practices: active or proposal.

Different execution tools, different isolation

PathActual limitsWhat you cannot conclude from it
run_pythonA default-deny macOS Seatbelt compute environment: only the reads the interpreter needs, read-write to a temporary workspace, and read-only reference files; network and host IPC denied, the child environment scrubbed, resource limits applied.It is not a general host for any Python program. Without working isolation it refuses by default; an unimplemented Linux sandbox is never treated as available.
Ordinary run_commandRuns a real shell. Policy has three layers: forbid (never, inside the sandbox or out: wiping the disk, the home folder or a system folder, raw disk writes, shutting the Mac down, reading passwords out of the keychain, touching Arslan's own key files, anything needing sudo); ask, this time only (sending or publishing, deleting outside the working folder, force-killing programs, changing startup or system settings, driving other apps, opening apps or links outside the sandbox, reading the screen or clipboard, writing shell-startup, SSH, credential or other agents' settings files, and any command whose text cannot be read, such as unbalanced quoting, eval or downloaded code piped into a shell); and run. Every command in a chain (&&, ;, |, ||, &) is checked, not only the first. A card for a command that matches a rule is answered for this time only: no answer is remembered for it. From 0.1.63 every new command asks first by default (the “When to ask” setting: “Ask before every command”); only with “Ask only for risky commands” does a command the policy answers run go ahead without a card: in the sandbox, or outside it where this conversation was allowed to leave it. The default workspace sandbox limits writes to the working folder, temp and cache folders, denies sending Apple Events, and closes the paths of Arslan's data and keys, the Hands socket and the user's credentials (SSH keys, Keychain files, cloud and CLI credentials such as AWS, gcloud, kube, gh, GnuPG, npm and netrc files, browser profiles, Safari and cookies). Since 0.1.59, with "Desktop, Documents, Downloads" off, it also closes those three folders to reads and writes, including through /System/Volumes/Data and symlinks; a working folder inside them stays read-write and active projects' folders stay read-only.Reading elsewhere, running programs and the network stay open, and a command the user lets run outside the sandbox is not held by the folder switch or by the closed credential paths (ssh, git push and gh need them); it runs under a minimal profile that still closes Arslan's own data, keys and the Hands folder and denies Apple Events. If the sandbox is unavailable, a command asks first (under “Ask only for risky commands”, a yes “In this conversation” covers that conversation’s later commands the policy answers run); once allowed, it runs without the sandbox and reports sandbox=unavailable, so this is not a uniform fail-closed path.
Installed MCP server (0.1.57)Starts under a deny-by-default Seatbelt profile: its own folder and temp, the folders you granted, Arslan's pinned runtimes read-only; your protected paths stay closed even inside a granted folder. HOME points into its own folder.The network is open only if it declared it needs a key for an online service, and then to any host (Seatbelt cannot hold it to one). Servers you added by hand are not moved into this sandbox in 0.1.57. Since 0.1.63 its tool calls ask by what the server says the tool does (chapter 08), and that statement is the server's own, not checked.
Offline wrap-up commandsOnce a task is wrapping up, it may process data already obtained and save the deliverable, with the network additionally blocked.If the required sandbox is unavailable it refuses; the ordinary commands' fallback does not apply.
AppleScript / ShortcutsIn a terminal command, osascript and shortcuts run always ask, this time only, in their common spellings (with a path, through command, exec or xargs, with redirection). The sandbox denies Apple Events, so a spelling Arslan does not recognise fails inside it. Only a plain call that has been approved runs with no sandbox wrapper: a script that talks to Reminders, Calendar, Notes or Contacts and does nothing else risky, or a plain shortcuts run whose files stay in Arslan's folder; it can be approved only by a click in the Mac window. Hands' own AppleScript tool asks every time with the whole script shown, is offered only to background work, and refuses a script that names a blocked app or chooses its app by a computed name.An approved Shortcut runs as the user outside the sandbox, and Arslan cannot see what it does. The command being inside the file sandbox does not mean the app it drives is held to the same file scope.
Arslan HandsA separate helper holds Accessibility; inside the helper, the app, action, field and stop state are checked again. Since 0.1.60 a look may carry a picture of that one window, held in memory only.An accessibility tree is not full business meaning, and a window picture is read by the model, not checked by code. Whether an app exposes actionable nodes affects what works; a window on another desktop is reached only by visiting it under the borrowing rules (chapter 08).

Leaving the command sandbox takes the user's click: either for the current action, or by choosing to allow it for the rest of this conversation — the latter is saved with the conversation (it outlasts a restart), covers its background tasks too, and can be taken back in Settings. Source development environments also have an explicit escape hatch for unsandboxed Python; the desktop app disables it. Neither should be read as the model being able to turn protections off.

Since 0.1.62 write_file and edit_file never follow a link out of the working folder: a write through a link that points outside is refused, a file with a second name elsewhere (a hard link) is replaced in the working folder and the other name keeps its old content, new files are never executable, and an existing file keeps its permissions. The file is written to a new file beside it and swapped in, so a failed write leaves the old one untouched. Since 0.1.63 the first change a turn makes to the working folder saves a checkpoint, and "Undo this turn" puts the files back and takes the turn out of the conversation. This is a recovery net, not a boundary: it saves only a folder of at most 20,000 files and 5 GB (not counting Python environments, node_modules, build output and caches, which it leaves out and never puts back), never copies secret-shaped files, covers only the working folder, undoes nothing that left the Mac, and says "Not saved" when it could not save. Checkpoints are kept for seven days.

constrained compute environment and the development escape hatch; workspace and protected-folder rules; installed servers' sandbox; ordinary-command fallback and offline refusal; command policy; writing without following links; checkpoints; undo this turn.

Approval is bound to the exact action; background work does not inherit every convenience

An approval card is tied to its call_id and action kind, so a reply of the wrong kind cannot approve a different card. A card shown in both the Mac window and on the phone takes the first valid answer and broadcasts the closed result to the other views; unanswered, it stays open for 30 minutes, and running out is not a no (since 0.1.61; earlier versions: it expired after 300 seconds). Since 0.1.61 one card looks the same in the window, the Inbox, the Island and on the iPhone: Once (or For this task, whichever the card offers first), In this conversation, Always, and No with a note that goes to Arslan; a message typed in the chat while a card waits answers it No, with that message as the note, and one click answers one card. Some kinds offer fewer choices: a command that matches a rule, money, a takeover and a risky step in an app answer this time only, and leaving the sandbox, a site, a Shortcut and an MCP tool never offer "Always". A job’s cards offer the same choices as the window’s; since 0.1.63 (S4a) a job started from a conversation uses that conversation’s grants, leaving the sandbox included. A run with nobody to ask (a scheduled run) takes none of these steps: no earlier answer counts there, the step is refused at once, and what it could not do is listed in the Inbox. In this version a scheduled run runs no terminal command at all. When the model suggests connecting an MCP server, registering a remote machine or reaching another execution entry, the proposal is handled separately from actually enabling the connection.

For external side effects, sending a request is not the same as completing an action. Resuming a task marks in-flight actions with unknown results as uncertain; they are not replayed automatically just because the process restarted. The goal is clear semantics for retry and recovery, not a promise that any external system can be undone.

Since 0.1.55 every waiting card — from a window, a background job or the iPhone — also stands in one queue and in the Inbox, and can be answered there by its call_id; the reply is built from the card's own kind, so it can only confirm or cancel that card, and the wider choices (In this conversation, Always) are chosen only on a card that offers them: the iPhone and the Island give only the card's first choice, never "Always", and the Inbox can give any choice the card itself offered. The Island can answer folder, app, screen, MCP tool, schedule, site and Shortcut cards and a command or workspace write that is not one of the risky kinds. It cannot approve money, sending, deleting, a risky step in an app, a takeover, leaving the sandbox, a command that matches a terminal ask rule or cannot be read, a command that also asks for the network, anything on a remote host, system changes, other agents' settings, installing a capability, an AppleScript, a command that would run with no sandbox around it, or any card kind the check does not know. Those show "Open in Arslan", and the server refuses an approval from the Island for them, not only the Island page. A card that would run an approved plain AppleScript or Shortcut with no sandbox wrapper, and one that installs a capability, are approved only in the Mac window; the iPhone's yes to such a card is swallowed. In the window, ⌘⏎ allows and Esc declines; plain ⏎ never answers a card.

answers from the Inbox and the Island; card binding, expiry and answers from several devices; uncertain side effects on restore; connection proposals and authorisation entry.

Hands keeps the system permission in a smaller process

Accessibility is granted to Arslan Hands.app, not the main app. The signed release accepts requests over a protected Unix socket, with a token generated at each launch and a peer signature check. The helper enforces the rules itself; the underlying agent-desktop does not hold the permission on its own. Since 0.1.61 looking at an app and acting in it are one card per app, offering "for this task" first. Since 0.1.60 a chat reply may also act: until 0.1.62 for up to five steps in one app, and from 0.1.63 under the run's own limits, as background work does, in more than one app, and Stop stops it (a scheduled run cannot act: nobody is there to approve it); a job started from a conversation inherits that conversation's yes. Acting in the browser, running Shortcuts and AppleScript are still offered only to background work. An app is judged by its identity, so naming it with .app or a full path, or in another case, does not get past the blocked list or a missing approval. Sensitive buttons — delete, send, pay, buy, transfer, submit — ask every time, by their actual label; password fields are never typed into, and apps such as password managers and system security screens are on a blocked list.

The stop control sets a stop latch that every later action must pass; the activity log keeps a trail of operations, recording typed text only by its length. The limits are public too: a window on another Space is reached only by visiting it under the borrowing rules, clicking at a screen position and dragging are not offered, reading a large window can take 10–20 seconds, and a call cut short by Stop may have no matching trace row — the audit trail is not a complete forensic record of every interruption.

Since 0.1.56 Hands runs each request at most once: answers to a request id are kept for ten minutes, a repeated id is answered from there, and the backend fetches an answer whose reply it lost instead of sending the action again. Appending to a field whose text cannot be read is refused rather than replacing it, Return is pressed only when that field has focus, and an action that was sent but not read back is reported as "sent, not confirmed", not done. Hands runs agent-desktop, built from Arslan's fork at a pinned commit; the Cua Driver engine was unwired from Hands in 0.1.63, so the helper no longer looks for or starts it (its unused module still sits in the helper's source). Since 0.1.60 one card can cover up to eight apps for a piece of work, a background job started from a conversation inherits that conversation's yes, and apps can be allowed for good in Settings → Hands. A click, choice or last menu item labelled to delete, send, pay, buy, submit, transfer, sign, post, publish or confirm (in several languages), the matching shortcut keys, Return in apps that send on Return, a takeover and any AppleScript still ask every time.

The local service has boundaries too. The release generates access tokens for REST and WebSocket, and installer acceptance checks that unauthenticated requests are refused. The Settings endpoint that shows the token gives it only to a direct loopback caller that already presents it (0.1.54); loopback alone is not enough, since a sandboxed command can open a loopback socket too. The desktop app's server always needs its token, accepts no other website as a caller (no cross-origin and no WebSocket without an Origin), trusts only localhost as a host name, ignores the host and origin variables and other settings inherited from the environment that launched it, serves no API explorer, and keeps the token out of its log (0.1.63). Source development mode allows a token-free localhost convenience path, though a host or origin variable naming * or null now refuses to start; "listening only on this machine" and "authenticated" are not the same thing. A host environment that is already compromised with the current user's permissions does not become trustworthy because of a local token.

socket, token, peer and stop checks; blocked apps and tiers; words and keys that always ask; Hands measurements and limits; API and WebSocket authentication.

10 / iPhone and Mac BridgeThe phone asks and watches; the Mac does the work

The mobile bridge lets a paired iPhone see the Mac's tasks, start work, answer approvals and read results. Execution still happens in Arslan on the Mac; the phone is neither a separate cloud agent service nor something that exposes or calls the Mac's HTTP interface directly. This chapter is based on the public protocol and source of Mac v0.1.63. The Mac Bridge shipped in earlier releases; its public release notes still describe the iPhone app as coming to the App Store, and this document does not claim an App Store release or full production end-to-end acceptance on that basis. Changes since 0.1.60 are marked with their version where they appear; the 0.1.61 notes say the Mac side works with Arslan for iPhone 1.0, which is still described as coming to the App Store.

iPhoneSwiftUI screens and device authentication
Encryption, outbox, status views
CloudKitPrivate database, ArslanBridge zone
Stores ciphertext envelopes asynchronously
Mac BridgeVerify, decrypt and map the protocol
Local HTTP / WebSocket
Arslan backendTasks, tools, approvals and files
Structured results return the same way

CloudKit is a mailbox; the protocol still establishes device identity

v1 uses the CloudKit private database under the same Apple ID. The Mac must be on, online and able to run; otherwise messages wait, and the phone cannot make a Mac that is switched off keep working. Bonjour on the local network is a later direction, not a fallback direct channel in this release. The Mac helper is a separate ArslanBridge.app that runs with the app, receives the local API token on stdin and then connects to the backend; the phone never holds that token or receives model API keys.

The envelope fields visible in the cloud index include the sending and receiving devices, sequence number, a coarse kind, a notification flag and time. The real message type, body and file content are inside application-level ciphertext. So "end-to-end encrypted" describes protection of messages between paired devices; it does not mean there is no metadata, nor that backend model calls bypass the AI service the user configured.

transport, envelope and Mac wiring protocol; CloudKit storage; Bridge runtime entry.

Scanning the QR code only starts pairing

The QR code carries a one-time pairing_id, the Mac's signing and exchange public keys, a 32-byte pairing key, the fixed container identifier and an expiry. It is valid for at most 600 seconds; the phone rejects expired codes, the wrong container and anything outside the constraints, and pins the Mac's public keys from the code. The phone then creates its own identity and exchange keys and sends an encrypted pairing request. Even after the Mac verifies the request, it shows it to the user, and a click on the Mac accepts or rejects it.

Retrying the same request must get the same decision, never a second grant. Once accepted, the devices communicate by identity and the pairing code is consumed; the Mac can revoke the phone. When the same iPhone pairs again it names its earlier ids, and the Mac removes those entries instead of keeping two. Removing a phone also deletes the messages the Mac had left for it in iCloud, and at start-up the Mac clears what still waits for phones no longer paired. Settings › Connections › iPhone shows each phone as Connecting… until the Mac has read an authenticated message from it, then Connected with when it was last seen. Each device keeps its private keys in its own Keychain. Trust here comes from one-time pairing material combined with the user's confirmation, not from "same iCloud account, so any message is trusted". Since 0.1.60, when the iPhone unpairs itself, the Mac forgets that phone as it would after Settings › Remove, ignores anything else from it and updates the list.

Every message is checked for content, sender and context

Each new message agrees a shared secret with an ephemeral X25519 key, derives a 32-byte key with HKDF-SHA256, encrypts and authenticates with ChaCha20-Poly1305, and is signed with Ed25519. The header uses length-prefixed UTF-8 fields, so differences in JSON ordering across languages cannot affect verification; header fields are bound by both the signature and the AEAD authentication. This is an application protocol built on CryptoKit primitives; it does not implement the Signal double ratchet, and forward secrecy in every scenario should not be claimed from ephemeral keys alone.

The receiving order is fixed: check the shape and the recipient, verify the signature, then decrypt; then check the plaintext against the header fields, and verify any attachment. A failure at any step must leave no partial business effect. Files are limited to 20 MiB, and the ciphertext digest, authentication tag, plaintext size and digest must all match before a file is cached or previewed.

pairing, cipher suite and verification order; Mac encoding and crypto protocol; one-time pairing and the user's decision.

Delivered, done and final on screen are three different things

MechanismResponsible forLimit
Persistence and same-packet retryA retry reuses the same envelope ID, seq and ciphertext; sequence numbers and other key state are saved first, so a network failure cannot roll state back.A delivery log cannot stand in for an external business system's transaction.
Replay windowPer sender, the last 1,024 sequence numbers are kept; new out-of-order packets inside the window are accepted, duplicates and packets that are too old are dropped.A valid new sequence number does not necessarily mean a business-state update; old progress may also be sent late.
ACKConfirms an envelope was delivered; a duplicate can be ACKed again. The original record is deleted once acknowledged, and ACKs themselves are not ACKed.An ACK does not mean the task succeeded or that an approved action was carried out.
Business IDs and resultsclient_msg_id and client_task_id prevent duplicates; approval.result, task events and results carry business state.In v0.1.63 the business-ID mapping has in-memory state, so global exactly-once across arbitrary crashes cannot be promised.

The Mac's status snapshot lists only tasks still running; the normal heartbeat is every 60 seconds, and the phone treats a Mac it has not heard from for 90 seconds as offline. Progress is throttled and merged; final states should be sent promptly. The client must therefore handle duplicate and out-of-order messages while making the task list, approval cards and history converge — "the last text received overwrites the previous one" is not enough to maintain state.

sequence numbers, processing records, ACKs and clean-up; business de-duplication and result mapping; snapshots and heartbeat.

Approving on the phone reuses the Mac's authorisation and execution path

The phone shows the exact action, target and risk. Approving requires the phone's biometrics; denying does not. After answering, the phone waits for the Mac's approval.result. The Mac checks that the card is still valid and maps the answer to the original confirm / cancel operation; the phone can never grant a command "always allow". The voice entry can produce text awaiting confirmation; it cannot stand in for approval.

Since 0.1.61 there is one approval card everywhere (chat window, Inbox, island and iPhone). The phone shows the card's own question, taken from the card's title, and what its yes covers. It answers once, or for this task where that is the card's first choice (cards for an app, a site or a Shortcut: the heading then ends with "(for this task)"), and never "In this conversation" or "Always". A card waits 30 minutes on the Mac (it was 5 before 0.1.61); running out is reported as expired, which Arslan treats as nobody answering, not as a refusal. A card that asks to install a new capability never reaches the phone. When the Mac answers first (in a window, the Inbox or the island), the phone's approval.result carries answered_on_mac.

Version boundary: since v0.1.54 the Mac counts an approval only with the faceid or touchid marker (Touch ID for iPhone SE), and says so in its capabilities; any other marker is a no. Before 0.1.54 a Touch ID yes was treated as a cancel. The protocol's high_risk_mac_only is fixed at false in this release and is not yet a user setting. Shared protocol and test vectors can verify interoperability and negative cases; they cannot replace end-to-end acceptance of the final distributed app with real iCloud, push and device authentication.

Files, live conversations and changing course from the phone

These are additive protocol types (section 5.7 of the protocol document); a Mac without them answers unknown_type, and the Mac sends the new events only to a phone whose hello capabilities asked for them.

  • Files from the phone (0.1.61). A file or photo sent from Arslan for iPhone (up to 20 MiB, at most 10 per message) is checked as any asset and saved on the Mac in the working folder under Inbox/iPhone, in a folder per date. Arslan reads it together with the message; pictures can go to the model as images. If a message's files do not all arrive within 120 seconds it is not run and the phone may send it again.
  • Live conversations (0.1.61). The Mac keeps its newest 20 conversations, and any with a running task, linked, so the phone lists them and follows answers, progress and cards while Arslan works. The phone can delete a conversation (the Remote conversation is never deleted). A history too long for one message drops its oldest messages and says it was truncated.
  • Answers as they are written (0.1.63). For a phone that asked, the reply arrives as partial events carrying the whole reply so far, at most once a second and not past 100 KB; the final message replaces them. Words typed on the Mac are shown on the phone marked as from the Mac, and never ring it.
  • Steering (0.1.63). A message sent while a reply runs reaches that reply at its next step instead of waiting; the iPhone, the Mac window and hold-to-talk all do this. While the reply is writing its final answer there is no step left, so the message runs right after it. Known limit in 0.1.63: a message added in the Mac window during a reply shows on the iPhone only from the next reply, and the iPhone keeps saying "Queued behind the current turn" after its own message was added.
  • Files to the phone (0.1.63). When you ask for a file (screenshot, chart, export, document), Arslan attaches it to its answer: a file card in the Mac window and the file itself on the iPhone, under the 20 MiB limit.

approval markers mapped to the original actions; clean-up when a phone is removed or re-paired; status, capability fields, inbox for phone files and live conversations; live links, files from the phone and partial replies; independent reference implementation of the protocol; 0.1.54 public delivery status.

11 / Verification and deliveryFrom source that runs to an app you can install and update

A desktop app is delivered as an app bundle. Python modules, system tools and old databases in a source environment can all make an installer that is missing resources look fine. Arslan splits verification into layers: source, platform, the frozen artifact, a brand-new user environment, and distribution. This chapter records v0.1.63's process and the matching release evidence.

CI checks behaviour, platform and dependency boundaries together

GroupMain contentWhy
Linux backendPython 3.11, uv frozen lock, Ruff, pytest; OCR test dependencies installed and checked explicitly.Runs the full backend suite, keeping the platform negative case "must refuse without macOS isolation".
macOSType-checking the Swift voice helper, building and testing Bridge, Rust fmt/test/clippy, Hands checks, native Python cases.Brings Vision, Seatbelt and host code on the platform that actually ships into routine verification.
FrontendNode 22, npm ci, TypeScript type-checking, Vitest, production build.Covers state and component behaviour, and confirms production assets can be built.
History and supply chaingitleaks scans the whole Git history with redacted output; Hands pins the upstream commit, checks vendored files, and runs cargo-deny on dependencies.Avoids checking only the latest diff, or a build quietly using changed external source.

The macOS tests do not just accept pytest's exit code. The pipeline parses the JUnit report and requires 60 named platform cases to actually run, with zero skips and no failures; if missing dependencies skip everything, it fails. Python and Rust share Hands contract samples, and the Mac, iOS and an independent Python reference implementation share Bridge vectors, so argument mapping, signatures, recipients, tampering and replay can be checked across languages.

These results still have a scope: Swift type-checking does not verify microphone authorisation, Linux unit tests do not verify the macOS kernel, and a simulated mailbox does not verify production CloudKit. Test counts are not overall coverage either; overlapping suites cannot be added up into independent scenarios.

four CI groups and the ran-for-real gate; cross-language Hands contract sample; Bridge reference implementation.

A release starts from an exact source commit

Source commitThe exact SHA to release
with successful CI
Build the appFrontend, Python sidecar
Swift / Rust helpers
Sign and notariseEmbedded binaries → app → DMG
notarise, staple, Gatekeeper
Check the real bundleIsolated brand-new HOME
auth, assets and run paths
Publish and updateSigned archive and manifest
Draft reviewed, then published

The release workflow checks that the SHA being released has successful CI; another commit passing does not count. The build uses lock-file-pinned dependencies; Python explicitly uses a uv-managed standalone interpreter so the runner cannot accidentally pick a framework build. The backend is frozen with PyInstaller onedir, and the frontend assets and Swift/Rust helpers are assembled into the Tauri app.

Signing and notarisation do different jobs. Embedded Mach-O binaries are signed first, then wrapped by the outer app; Developer ID, hardened runtime and timestamps are for distribution. The DMG is submitted to Apple for notarisation, then the ticket is stapled and Gatekeeper checks it. The build script keeps an unsigned development path and a warning path for missing credentials, so "it builds" does not automatically mean "it meets the conditions for public distribution"; a real release must check the artifact it actually produced.

The update manifest has its own constraints: the tag must match the Tauri version, and a manifest is generated only when there is an updater signature. The workflow creates a GitHub draft release; only after it is published do installed apps see the update on their next check.

exact-SHA gate, version check and draft release; freeze, assemble, sign, notarise, and the fallback branches; PyInstaller dependencies and resources.

Fresh-install acceptance launches the real app bundle

fresh_install_check.py launches the real arslan-desktop inside the bundle with a temporary HOME and a minimal environment, rather than starting the service from a source venv on its behalf. It checks that no user database or secret files were bundled, first-launch initialisation, token generation and file permissions, that unauthenticated requests are refused, a real WebSocket 101 handshake, and that the frontend JavaScript can actually be fetched. An HTTP 200 may just be the SPA's fallback page, so asset checks cannot rely on status codes alone.

The same script also checks native OCR, the embedded helpers, the Bridge self-test and the relevant signatures. A frozen bundle once missed a dynamically loaded WebSocket dependency: the health endpoint was fine, but chat could not upgrade its connection. That is why a successful import, a live service and core features shipping in the bundle are three different kinds of evidence.

launching the app and in-bundle acceptance; explicit collection of the dynamic WebSocket dependency.

Updates install when you choose, and downloads are verified

The Tauri updater finds updates from the public latest.json and verifies the archive's signature with a public key compiled into the app. The app checks once at launch, and you can check manually from the menu; this release has no periodic check timer. When a new version is found it shows a notice; only when you click Install does it download, verify, replace and restart. An automatic check failing may just mean you are offline; a failed install must be reported clearly.

The early 0.1.0 and 0.1.1 only registered the updater plugin and never called the check, so they could not discover new versions. The current implementation chains checking, installable state, download failure and restart into an explicit flow; having a signing configuration does not mean the flow has been triggered.

check, install and restart state; update URL and public key configuration.

Which object 0.1.63's delivery evidence applies to

The GitHub Actions runs for the release commit show: the release source is 9bbb6ad6 on main; CI on main passed on its first attempt (run 40502155919), including the macOS job's 60 named platform cases with zero skips and the backend's 9,469 passed and 124 skipped. Pushing the tag started no CI run of its own, so the gate used the run on main for the same commit. The release workflow (run 40505601007) passed source verification, build and publish; the DMG was accepted by notarisation, stapled, and accepted by Gatekeeper. The shipped app's fresh-install check had 92 passed and 0 failed, including Hands accepting the signed backend's connection and the Bridge self-test.

The two releases before it: 0.1.61 was cut from main at fb6d7230 (CI on main run 38489348597, tag CI run 38498094635, release run 38498094628; 54 platform cases; backend 8,466 passed and 124 skipped; fresh install 89/0). 0.1.62 was cut from release/0.1.62 at a4fca23b (branch CI run 39112567104, tag CI run 39114206598, release run 39114206588; 55 platform cases; backend 8,747 passed and 125 skipped; fresh install 89/0). Each passed on its first attempt and was notarised, stapled and accepted by Gatekeeper.

The objects differ, and the records say so. 0.1.63's public notes say a short list of checks was gone through by hand on a release candidate and that two of them did not pass (a third follow-up edit that changed more than asked, and a one-line change in a long file made through the terminal); they do not say the exact 0.1.63 DMG was installed and walked through by hand, and neither does this document, nor a full iPhone-to-Mac run with the shipped app. The notes of 0.1.61 and 0.1.62 contain no hand-run on a real Mac that this document relies on. For comparison, 0.1.53's Verification section recorded a real-Mac run in which Hands created and read back a note in Notes, with an approval left waiting for 36 seconds — on the build-only bundle of 43751254, not the final DMG. Automated bundle acceptance, feature runs on an earlier build, and manual acceptance of the exact release bundle are stated separately; none substitutes for another.

12 / Testing and evaluationHow to tell whether a task was done right

A task that finished, a file that opens and a report whose content is correct are three different judgements. Arslan uses run records to show what happened, checks deliverables and contracts to verify what can be determined, and uses real-task evaluations to observe how models perform. Passing one layer cannot stand in for the others.

This section brings together the public source, release notes and past experiment records. The numbers belong to their own versions and environments; they do not mean every test was re-run while this was written, or that every feature has been accepted on real devices with real models.

First, break "done" into checkable evidence

Did it actually run?

Did the tool call return? What was the exit code? Did the action clearly succeed, clearly fail, or is the outcome unknown? A model writing "done" cannot replace tool evidence, and a side effect with an unknown outcome cannot be treated as safe to run again.

Does the deliverable hold up?

Does the file exist, belong to this task, match its byte count and hash, and parse? A spreadsheet that opens does not mean its data is right; an image of the right size does not mean its layout is acceptable.

Was the requirement met?

If ten valid results were asked for, six do not meet it. Citations must match conclusions; having read a page does not prove it supports a claim. This layer still needs task-specific checks and human judgement.

The task acceptance system keeps rule checks, model evaluation and human review distinct. Text, JSON, file integrity, image size and actual execution results can be checked deterministically; model evaluation covers only what it is suited to judge, and cannot turn a check that never ran into a pass. A deliverable that parses proves its structure is readable, and a record of reading a source proves the system got the content; neither automatically proves facts or visual quality.

acceptance rules and the limits of deliverable checks; 0.1.53: unfinished tasks are shown as "Not done".

Test layers: each answers one question

Layer of evidenceWhat it checks, with examplesWhat you cannot conclude from it
Unit and contractArgument validation, state transitions, error classification, serialisation, permission decisions; valid inputs plus negative cases such as tampering and replayThat a real model will choose the right tool, or that every third-party service stays compatible
Service integrationDatabase, run records, cancellation, deliverable export, recovery and permission callbacks really connected; the model can be a deterministic stand-inThat a stand-in passing means a real model completed a natural-language task
Platform runsThe macOS sandbox and system interfaces actually run in a specified environment; missing dependencies or skips do not count as passingThat every OS version, permission combination and app is compatible
Final installerFrozen dependencies, frontend assets, authentication, a real WebSocket handshake and helpers ship in the installerThat the completion rate and interaction quality of every real task are verified
Real-task evaluationFixed model, tasks, permissions and check rules, run several times, keeping successes and failuresThat a small sample represents an overall success rate, or that it is generally better than other products
Device acceptance of the exact artifactThe specified distributed bundle completes the path on a real device, with real permission states and servicesThat a neighbouring build or a simulator passing replaces all device checks of this bundle

For example, the September 2026 reliability work provided 30 fixed contracts with zero external model cost; they really exercise storage, cancellation, deliverables, backup and permission code, but use a deterministic provider stand-in. That is engineering-contract evidence, not 30 end-to-end tasks with a real model. The later paid evaluations are a different layer of evidence, and the two cannot be blended into one success rate.

Tests also have to prove they actually ran. v0.1.63's macOS CI parses the JUnit report and requires 60 named platform cases to actually run with zero skips. Hands' Python and Rust sides share contract samples; both ends of Bridge share protocol vectors. For key protections, mutation checks temporarily break a rule and confirm the tests turn red, exposing tests that only check how something is implemented rather than constraining what it does.

engineering contracts versus model quality; platform case count and skip gate; regression and mutation checks for source review.

Release verification is bound to the source and the artifact

Source and CIBackend / frontend / macOS
whole-history secret scan
Same-SHA gateThe commit to release
has matching successful CI
Build and signSPA, Python, helpers
app and installer
Install acceptancenotarise, staple, Gatekeeper
real launch in isolation
Distribute and updateRelease, update manifest
signed update archive

The fresh-install check launches the app with an isolated, brand-new HOME and covers authentication, a real WebSocket 101 handshake, frontend assets, OCR and helpers. A health endpoint returning 200 is not enough: an early installer once could not open a chat connection at all while its HTTP service looked fine.

v0.1.63's release corresponds to commit 9bbb6ad6: CI on main passed on the first attempt (run 40502155919; the tag push started no CI run of its own); the release workflow's (run 40505601007) source verification, build and Release steps succeeded; the DMG was accepted by notarisation and stapled, and Gatekeeper accepted it. The shipped app's fresh-install check had 92 passed, 0 failed, including Hands starting and the Bridge self-test. (0.1.61 and 0.1.62 each passed with 89 of 89: runs run 38498094628 and run 39114206588. 0.1.53 needed a second CI attempt because of an order-dependent flake; 0.1.54 fixed its cause, tests leaking a shared database session, and added a guard that fails any test that does.)

The on-device interaction evidence comes from a different, clearly labelled build. In the Notes test, a background job created and read back a note through Hands, and continued after the first approval card waited 36 seconds; a file-rename task used mv without touching a Finder window. These tests used an earlier build-only bundle at commit 43751254, which differs from the release commit in its version number and documentation. It is not the same SHA as the final release; 0.1.60's release notes also disclosed that nobody installed the exact final DMG by hand and went through it fully before publishing. The install check and the Notes run on an earlier build are stated separately.

0.1.63's own pre-release test missed two bars, and the notes say so. Nine tasks, three runs each, on DeepSeek and on Claude, with "Ask only for risky commands" on: the task "find another way after a refused delete" passed 0 of 3 on Claude (3 of 3 in 0.1.62), because a delete by name after changing into the working folder ran without a card on purpose, since "Undo this turn" can bring the files back; and the task "three follow-up edits to one document" passed 1 of 3 on DeepSeek and 0 of 3 on Claude, because the checker wants a section cut below 60% of its length and the task only asks to make it shorter (the checker was kept). The shipped default, asking before every new command, is not what those rounds ran. Results of this kind locate weak spots; they are not a ranking.

0.1.63 release notes; release pipeline; install check; packaging and signing.

Real-task data: keep the comparison, and the weak spots

The 0.1.51 evaluation on 3 October 2026 used deepseek-v4-pro, compared against Hermes in the same round, with three runs of each of four tasks. "Passed acceptance" means checker score = 3, which is not the same as the run's status being done. Cost and time are the mean of each task's three runs, including failures; cost is at that round's off-peak prices. Time is the span from the first model request starting to the last one ending — neither the full end-to-end task time nor pure model inference time.

TaskArslan passed / 3Hermes passed / 3Mean cost and model-call span: Arslan / HermesWhat it shows
Reminders3/32/3$0.025, 76 s / $0.058, 142 sA good round; the sample is still very small
Job research table1/33/3$0.167, 313 s / $0.093, 160 sGaps remain in the number of results and research completeness
Apple financials table3/33/3$0.041, 77 s / $0.026, 36 sBoth passed; the comparison had a shorter span and lower cost
Organise Downloads3/33/3$0.053, 113 s / $0.046, 100 sSame number passed

This experiment is useful for locating what to improve, not for an overall product ranking. Model, task, environment and approval policy all affect the results. The round also ran out of credit and was resumed; four zero-cost failed requests were removed from the usage log used for timing, and the original log was kept. The public experiment record keeps these conditions, and they should be read together with the summary.

Comparisons between versions need the same restraint: the Reminders sample went from 1/3 in 0.1.49 to 3/3 in 0.1.50, a stage in which alternative routes and context state improved. But several things changed between those versions, and the whole difference cannot be strictly attributed to one prompt or one module.

0.1.51 experiment method and process; four-task summary; staged evaluation of alternative routes.

Model review needs reviewing too

In 0.1.41's source-review calibration, 13 reports produced 14 hints: 1 correct, 2 borderline, 11 wrong — and it missed both pre-registered known errors. So the feature stays off by default. Calling another model does not automatically give you a reliable fact-checker; valid format and existing anchors do not guarantee a critique holds.

0.1.52's learned-practice validation shows a different limit: after an AppleScript was refused once, the practice was saved and recalled, and next time it went straight to EventKit — showing practices can influence execution. But later script editing and checking made more calls, and no speed-up was measured. To judge whether a practice itself improves efficiency needs paired, repeated runs of the same task with and without it; the record at the time states plainly that this fair speed comparison had not been run.

Stronger quality claims need tasks, deliverables and checks frozen in advance; the model and configuration, call trajectories, real costs and failure reasons recorded; and model scores calibrated against independent human labels. A malformed score, a check that never ran and evidence that could not be obtained each keep their own status, rather than being counted as passes to make the summary look better.

public calibration result for source review; what learned practices passed, and what is unproven; conditions for stronger quality claims.

13 / Engineering directionsFrom what exists to more reliable delivery

Arslan has three foundations: execution, ongoing work and memory. What is worth verifying next is whether these hold correct state, give reliable results and leave the user in control in more complex situations. The diagram separates what is implemented, what still needs verifying and optional engineering directions; it is a technical discussion, not a dated release commitment. The chapter reflects v0.1.63; what is planned for 0.1.64 is marked as planned, not built.

Planned for 0.1.64 (a plan, not built; from the approved 0.1.64 plan): first, a sandbox for commands, Python and installed tools that denies by default. Then "Ask only for risky commands" comes back. After that comes an optional "auto" mode, in which a reviewer model answers some cards: it is off by default, ships only if it meets pass lines set before anything is measured, and never approves anything that leaves the sandbox, sends something out or goes to the network. There would be five modes, chosen per conversation next to the input box: plan, ask every step, auto-edit, full access and auto; Settings would keep only the default for new conversations. Scheduled tasks would get read-only commands back. Until then every new command asks first, and scheduled tasks run no commands. A page that lists and undoes remembered answers, and an allowed-folders setting, are under consideration, not in the plan. The 0.1.63 notes also list two fixes for known limits: a delete after changing folder in the same command is wrongly treated as inside the working folder, and a one-line change in a long file goes through the terminal instead of the edit tool.

Arslan delivery blueprint Implemented · v0.1.63Still to verifyNext · no committed dates Can actSingle loop, files, terminal, web, HandsStreaming, steering, results flow back Continue across devicesiOS app release, final-state syncDistribution and full path need acceptance Higher real-task completionResearch completeness, source checks firstRegress continuously on a fixed task set Keeps goingJobs, dollar cap, undo a turn, resumeResident, scheduled, proactive suggestions Recovery state convergesReplay, approvals, final states, cachesRestarts and reordering need evidence A provable recovery contractStable IDs, crash tests, side-effect checksState publicly where retry and resume apply RemembersPreferences, machine notes, practices, RAGSources, permissions, proposals, undo Learning needs wider proofPractices recalled and followed, seenNot yet shown to be faster or cheaper Controlled learning trialsSame task, with and without, repeatedSuccess, cost and wrong permissions The baseline for every stage: visible, authorised, stoppable, traceableWorkspace and system permissions / real execution evidence / budget and cost / user data control / same-source CI and bundle acceptanceProduct goal: a closed loop from intent to delivered result; feature or version counts never stand in for task results.

Figure 4 · Capability blueprint. As text: three foundations — execution, ongoing work, memory → cross-device and reliability acceptance → suggested priorities: real completion rate, a recovery contract, controlled learning trials. Suggestions are not shipped commitments.

Real-task results

A fixed task set should cover search completeness, citations matching conclusions, files that open, where system operations actually land, and whether a failure is wrapped up honestly. The existing comparison exposed gaps in research tasks, and the 0.1.63 pre-release run (nine tasks, three runs each, on DeepSeek and Claude) missed two of its own bars: finding another way after a refused delete (Claude 0 of 3, because a delete inside the working folder runs without a card under "Ask only for risky commands") and three follow-up edits to one document (DeepSeek 1 of 3, Claude 0 of 3); before widening the tool list, locating at which step "not found, not fully read, not verified, not delivered correctly" happens does more for the completion rate.

Recovery and consistency across devices

Network retries, duplicate messages, app restarts and user cancellation can happen at the same time. Further verification can centre on stable business IDs, final states that old messages cannot overwrite, behaviour after an approval is cancelled, and the checking path when a side effect is unknown. Crash injection and protocol negative cases can make clear which cases can be handled automatically and which must go back to the user. One piece has shipped: since 0.1.63 a checkpoint is saved before a turn first changes the working folder, and "Undo this turn" restores the files and removes the turn. It is limited: it saves only a folder of at most 20,000 files and 5 GB (not counting Python environments, node_modules, build output and Python caches), and says "Not saved" otherwise; it covers the working folder only, not effects outside it.

Measurable gains from learning

A practice being remembered is only the start. Completion rate, call cost, time and wrong behaviour need comparing repeatedly, with and without the practice, on the same tasks and models. It also needs checking whether stale practices get corrected, whether external text can contaminate practices, and whether a deleted practice is still recalled.

The cost of platforms and extensions

Tauri provides a cross-platform host, but Seatbelt, Vision, Accessibility, Apple Events and CloudKit are all platform-specific. Moving to another desktop system means replacing and verifying these capabilities; a cross-platform compile is not the same as the product's features having moved. More external tools likewise mean more credentials, permissions and failure modes.

These directions are drawn from the current implementation and test results. Actual priorities and dates are whatever later release notes say.

14 / GlossaryFrom terms back to the implementation

Terms here distinguish different mechanisms. The tables give a short definition and place each concept back in Arslan's implementation; historical mechanisms are marked as such.

AI and context

TermIn plain wordsHow Arslan uses it
LLMA large model that understands and generates languageFrom the provider the user configures; Arslan provides the app and the execution system
AgentA process that decides its next step from resultsCall the model, run tools, look at results, continue or wrap up
OrchestratorA coordinator that brings different capabilities togetherCoordinates the model, context, tools, permissions and task state
Tool callingThe model requests a tool in a structured wayTool name and JSON arguments are validated before anything runs
Native tool protocolThe tool message format a provider officially definesKeeps tool call IDs, results and reasoning continuation correctly matched
TrajectoryA structured record of the model's interaction with toolsOpenAI-compatible providers use native history; other providers keep their own paths
Context windowHow much a model can see at onceAll history, all memory and full long output cannot be stuffed in without limit
CompactionCompressing old context into a bounded summaryControls request size while keeping recent content; it does not delete the original conversation
RAGFind relevant material first, then let the model answerRetrieves passages from imported knowledge, with their sources
FTS5SQLite's full-text searchGood at word matches; material can be searched even without vectors
EmbeddingTurning the meaning of text into a numeric vectorHelps find content that is worded differently but means something similar
Cosine similarityComparing whether vectors point the same wayOne way of measuring similarity for semantic search
RRFMerging several retrieval results by rankFuses FTS and vector rankings without mixing scores in different units
SkillA reusable "how to do it" guideSKILL.md with optional scripts and references; not another model
MCPA standard interface between an agent and external toolsConnects external capabilities over stdio or HTTP; the app still controls authorisation. Since 0.1.63 a tool its server marks read-only runs without a card, a destructive one asks every time, and the rest ask once per conversation
On-demand toolsTools that are not sent with every request but looked up when neededSince 0.1.63 rarely used tools and MCP tools are not in every request; the model finds them (find_tools) and calls them (use_tool) under their real names, with the same approvals
StreamingShowing an answer as the model writes itSince 0.1.63 for OpenAI-compatible providers, Claude and Gemini; if a stream breaks halfway, what you saw is kept and the reply is marked interrupted. Checks that used to hold the answer back now add a correction after it
SteeringRedirecting a task while it is runningSince 0.1.63 a message typed during a reply reaches it at its next step; one sent while the final answer is being written runs right after it
Dollar limit per taskA cap on model spend for one taskOn by default since 0.1.63: $2 per chat reply or background task, wrapping up at about two thirds; only models with a known price count; changed in Settings › Background
BYOKBring your own model API keyYou choose the provider and pay for the model calls
Prompt injectionWeb or tool content disguised as new instructions for the agentExternal content is treated as data, and practices influenced by it without confirmation usually become proposals; this is not proof of immunity
Learned practiceA method noted down from a correction or a detour that worked"AppleScript doesn't work on this Mac — try EventKit"; model weights are not changed
Shadow modeRecording a judgement without letting it change behaviour yetThe judgement layer keeps its ledger for small decisions; the shadow approval suggestion for command cards was removed in 0.1.63, and every card waits for your click
Held-out evaluationTesting on questions not used to tune the promptA historical design from early expert evolution, not a fixed step in today's turns

Software architecture, state and reliability

TermIn plain wordsHow Arslan uses it
SPASwitching pages and state inside one web appThe local service serves the React workbench's static assets
SidecarA helper process launched alongside the main appThe local Python backend running next to Tauri
IPCCommunication between processes on the same computerPipes, Unix sockets; not every module talks through the cloud
REST / HTTPOne request reads or changes one resourceSettings, history, task and file management APIs
WebSocketA two-way connection that keeps pushing updatesReply streams, tool status, approvals and progress events
Async I/OYielding the event loop while waiting on I/O so other work can proceedPython asyncio, async APIs and database access; not unlimited parallel CPU work
Schema / ContractAn explicit agreement on inputs, outputs and valid statesPydantic, tool arguments and task acceptance contracts
State machineDescribing how far a process has got with a finite set of statesqueued, running, waiting_user, verifying, succeeded and so on
CheckpointA record of where things are, what was done and what is leftResuming uses references, evidence and budget; unknown side effects are not blindly replayed. A second kind since 0.1.63: a saved copy of the working folder taken before a turn first changes it, which "Undo this turn" restores (only for a folder of at most 20,000 files and 5 GB; otherwise the undo window says "Not saved")
AttemptOne run of a taskResuming creates a new attempt; old async results cannot overwrite the new round's state
Optimistic concurrencyChecking before writing that the data is still the version you sawRow version and attempt ID stop old results overwriting a cancellation or update
IdempotencyA repeated request should not cause an unintended repeated actionMessage and task IDs prevent duplicates; unknown external effects still need checking
ACKThe receiver saying "I got this packet"Does not mean the task succeeded or that an approval took effect
Single-flightNot starting the same thing again until the last run finishesScheduled tasks avoid piling up repeated runs
BackoffWaiting longer between retries after failuresBacks off on rate limits and server errors, honouring Retry-After; authentication errors are not blindly retried
WALSQLite writes a log first, so reads and writes coexist betterLocal persistence; write concurrency still has limits
MigrationSafely upgrading an old database structure to a new oneAn explicit versioned runner, a backup before upgrading, unknown schemas refused
ProvenanceWhere a piece of information came fromDocument sources, memory sources, real run trajectories
ObservabilityBeing able to see what happened when something goes wrongRuns/Steps, Activity, usage, errors linked to deliverables

Operating system, security and delivery

TermIn plain wordsHow Arslan uses it
Sandbox / SeatbeltThe operating system limits what a process can reachThe compute sandbox and the command sandbox have different rules; network and fallback behaviour are described separately
Fail-closedRefuse to run when the protection is unavailableApplies to the default Python sandbox and the offline wrap-up path; not to every ordinary command
Accessibility treeThe structure of windows, buttons and text that the system exposesHands reads and locates controls with it, not by recognising screenshot pixels
TCCmacOS's privacy permission systemAccessibility, microphone, Apple Events and others are not granted by the app flipping its own switch
SSRFTricking a service into reaching internal addresses it shouldn'tWeb extraction and external URLs need their target, redirects and network boundary checked
AEADEncryption that protects both secrecy and integrityBridge uses ChaCha20-Poly1305; tampering makes verification fail
Key agreement / SignatureThe first agrees encryption material; the second verifies who sent itX25519 and Ed25519 do different jobs and should not be conflated
Replay protectionStopping an attacker from re-sending an old message to make it count againSignatures, recipients, a sliding sequence window and persistent de-duplication work together
CI / CDAutomating build, checks and distributionSuccessful CI must belong to the SHA being released, and the final artifact is checked too
Contract testChecking both sides of a boundary follow the same protocolPython/Rust and Mac/iPhone share positive and negative vectors
Mutation testingDeliberately breaking a protection to see whether tests catch itShows a specific assertion actually discriminates; not just a unit test under another name
Code signingVerifying where software came from and that it was not modifiedThe app and its embedded helpers all need correct signatures
Notarization / StapleApple checks the release, then the ticket is attached to itDifferent from signing; Gatekeeper also checks the result
Fresh-install acceptanceSimulating a new user with no development environment and no old dataVerifies authentication, WebSocket, assets and helpers in the real installer

Today's system is mainly a layered local monolith plus separate helpers. SQLite and in-process async scheduling suit how a personal desktop product is installed and run; read these terms at that deployment scale.

15 / FAQWhat is easiest to mix up when using and understanding Arslan

How is Arslan different from chatting with a model on its website?

Beyond the model, Arslan provides local files, tools, task state, memory, permissions and desktop integration. The model judges and generates; the app turns tool requests into controlled execution and records the results. Its value depends on whether the whole process gets a specific task done.

Was it built by one person with AI?

Yes. The author builds and iterates on Arslan alone with AI-assisted development — vibe coding. The product uses open-source frameworks, third-party components and external models, whose sources and licences are recorded in the project documents.

Does everything stay on my Mac?

Data is primarily stored, and execution orchestrated, on your Mac. When you use a cloud model, search, a remote MCP server or similar, the relevant requests go to that service. Memory also has its own cloud-use permission and sensitivity rules; local-first does not mean always offline. Since 0.1.63 a Claude conversation on Anthropic's own API searches with Anthropic's own web search. A conversation on a local model never sends its queries to a provider's own search; Tavily or SearXNG chosen in Settings can be the only search or a backup, and with no deliberate choice, a best-effort DuckDuckGo scrape comes last.

Can I switch models, or use a local one?

You can configure a supported provider or a compatible endpoint, including an OpenAI-compatible local server. An endpoint connecting only proves the transport works; tool calling, images, reasoning continuation and context length still depend on that model and its adapter path.

Early docs mention many expert personas — why is there only one Arslan now?

The product converged. 0.1.44 stopped creating and dispatching experts, 0.1.46 turned off the old automatic evolution loop, and 0.1.48 removed the front router. 0.1.63 then deleted the expert-era code no reply could reach any more (creating experts, their templates and engine, the evolution and curation machinery, the research review, and the conversation menu's "Distill"); tasks started by the old experts are continued by Arslan when resumed. Old methods can be converted into Skills, historical data stays compatible, and constrained read-only workers are still used for independent research.

Are memory, the knowledge base and chat history the same thing?

No. Chat history records conversations, and the working context picks recent content plus a summary; personal memory keeps preferences, facts and rules with their sources and permissions; knowledge retrieval finds relevant passages in imported material. The three differ in how they are stored, recalled and deleted.

Does "learning" train the model, or let it give itself more permissions?

No. Learning today records reusable methods and recalls them in later tasks; it does not change foundation-model weights or execution permissions. The records show practices can be captured, recalled and followed; a general speed-up or cost saving still needs more controlled experiments.

Do all commands run in an offline sandbox?

No. The default Python compute sandbox denies the network; the ordinary terminal's workspace sandbox usually keeps network access and has its own fallback behaviour. Tool permissions, command policy, system permissions and sandboxes are different layers; the security chapter has the details. Since 0.1.63 every new terminal command shows a card first by default ("Ask only for risky commands" is an explicit choice that says what it misses), and scheduled tasks do not run commands at all: such a step is skipped and listed in the Inbox.

What happens to a task when I close the chat window?

Connections and tasks are managed separately, and a background job can run while the app's service keeps running; closing the window is not the same as quitting the process. Since 0.1.63 long work stays in the chat rather than being moved to the background automatically, and after ten minutes a reply tells you it is still working. If the service exits or the Mac sleeps, the task cannot keep relying on local execution. After a restart, interruptions are checked from the records, and the task resumes explicitly along a supported path.

Does resuming automatically re-run every earlier action?

No. Resuming checks the steps already taken and their external effects, and unknown side effects are checked first. Task attempts, action states, checkpoints and version constraints help stop old results overwriting new state, but they cannot guarantee exactly-once execution for every external system.

Can Hands operate any Mac app?

Compatibility depends on the Accessibility structure the app exposes, system permissions and Arslan's action rules. Being able to read a control does not mean every business operation can be completed safely. Sensitive buttons and keys trigger finer-grained confirmation, and some apps or controls may still not be supported. Since 0.1.61 Hands asks once to look at an app and act in it (for this task first); deleting, sending, paying and submitting still ask every time, and so does Hands' AppleScript tool.

Can the iPhone get work done while the Mac is off?

On the Mac Bridge path the Mac still does the work; when it is unavailable, messages wait in iCloud. v0.1.53 shipped the Mac side of Bridge and v0.1.54 readied it for the iPhone app, and 0.1.61 added files from the phone and live conversations on the Mac side; the release notes still describe Arslan for iPhone 1.0 as coming to the App Store, which does not mean it is there. The phone showing a message as received does not mean the task is done either.

Can I undo what Arslan did to my files?

Partly. Since 0.1.63 Arslan saves a checkpoint before a turn first changes your working folder, and "Undo this turn" (under ⋯ on the reply) puts the files back and takes the turn out of the conversation. It saves the folder only if it has at most 20,000 files and 5 GB, not counting environments, node_modules, build output and caches, and the undo window says "Not saved" when it could not. It covers the working folder, not effects elsewhere such as sent messages.

Can I stop a task from spending too much?

Each chat reply and each background task stops at $2 of model spend by default since 0.1.63, starting to wrap up at about two thirds. Change it in Settings › Background › Dollar limit per task. Only models with a known price count towards it.

Can I change course while Arslan is working?

Yes, since 0.1.63. A message typed in the Mac window, on the iPhone or by hold-to-talk reaches the running reply at its next step, and the steps not yet started are skipped. If the reply is already writing its final answer, your message runs right after it.

If all tests pass, can any real task be done?

Each test only covers its own conditions: unit tests check local logic, protocol tests check boundary consistency, installer acceptance checks the artifact, and real-task comparisons check results for a given model and task. Models, web pages, system permissions and external apps can all change the outcome.

Why is the version still 0.1.x?

The project is still in pre-v1 continuous iteration. More than fifty version numbers reflect that history; judge maturity by specific capabilities, failure handling, test coverage and release status. The full version appendix in this document keeps those distinctions.

16 / Version appendixEvery change from 0.1.0 to 0.1.63

This appendix covers the 64 stable-format version tags v0.1.0–v0.1.63, plus the 0.1.40 preview branch and the 0.1.59 betas. Going by the public release records checked on 27 October 2026, GitHub currently shows 63 releases and 6 prereleases, 69 in all. v0.1.39 has a version tag but currently no public release.

The latest release, v0.1.63, was published at 22:26:31 Beijing time on 27 October 2026 (14:26:31 UTC on 27 October). The table uses Beijing-time dates throughout. Apart from v0.1.39's tag date, which is marked, dates come from each release's published_at; commit dates are never used in place of publication dates.

Version changes are not a running total of today's capabilities. The table describes what changed in each version at the time. Expert orchestration was retired in 0.1.44–0.1.48, and old expert methods can be converted into Skills; 0.1.32's separate read-aloud switch was also removed in 0.1.48. For current behaviour, go by 0.1.63's implementation and its stated limits.
Every release, to scale2026-07-27 → 2026-10-27
AugSepOct27 Julv0.1.0 · 2026-07-27 · 1,125 commitsv0.1.1 · 2026-07-27 · 4 commitsv0.1.2 · 2026-07-27 · 1 commitsv0.1.3 · 2026-07-27 · 2 commitsv0.1.4 · 2026-07-27 · 7 commitsv0.1.5 · 2026-07-27 · 2 commitsv0.1.6 · 2026-07-27 · 2 commitsv0.1.10 · 2026-07-28 · 2 commitsv0.1.7 · 2026-07-28 · 5 commitsv0.1.8 · 2026-07-28 · 2 commitsv0.1.9 · 2026-07-28 · 9 commitsv0.1.11 · 2026-07-29 · 7 commitsv0.1.12 · 2026-07-31 · 16 commitsv0.1.13 · 2026-08-01 · 11 commitsv0.1.14 · 2026-08-01 · 2 commitsv0.1.15 · 2026-08-02 · 2 commitsv0.1.16 · 2026-08-03 · 7 commitsv0.1.17 · 2026-08-03 · 3 commitsv0.1.18 · 2026-08-03 · 4 commitsv0.1.19 · 2026-08-04 · 6 commitsv0.1.20 · 2026-08-09 · 60 commitsv0.1.21 · 2026-08-13 · 8 commitsv0.1.22 · 2026-08-16 · 12 commitsv0.1.23 · 2026-08-17 · 3 commitsv0.1.24 · 2026-08-20 · 6 commitsv0.1.25 · 2026-08-20 · 4 commitsv0.1.26 · 2026-08-21 · 5 commitsv0.1.27 · 2026-08-21 · 5 commitsv0.1.28 · 2026-08-23 · 3 commitsv0.1.29 · 2026-08-23 · 5 commitsv0.1.30 · 2026-08-31 · 2 commitsv0.1.31 · 2026-08-31 · 2 commitsv0.1.32 · 2026-08-31 · 2 commitsv0.1.33 · 2026-09-01 · 2 commitsv0.1.34 · 2026-09-01 · 2 commitsv0.1.35 · 2026-09-02 · 2 commitsv0.1.36 · 2026-09-02 · 2 commitsv0.1.37 · 2026-09-03 · 5 commitsv0.1.38 · 2026-09-14 · 18 commitsv0.1.39 · 2026-09-14 · 3 commits · tag only, no public releasev0.1.40-beta.3 · 2026-09-20 · prereleasev0.1.40-beta.5 · 2026-09-23 · prereleasev0.1.40-beta.6 · 2026-09-23 · prereleasev0.1.40 · 2026-09-26 · 267 commitsv0.1.41 · 2026-09-27 · 6 commitsv0.1.42 · 2026-09-28 · 9 commitsv0.1.43 · 2026-09-30 · 4 commitsv0.1.44 · 2026-09-30 · 4 commitsv0.1.45 · 2026-09-30 · 3 commitsv0.1.46 · 2026-09-30 · 3 commitsv0.1.47 · 2026-09-30 · 12 commitsv0.1.48 · 2026-10-01 · 13 commitsv0.1.49 · 2026-10-02 · 22 commitsv0.1.50 · 2026-10-03 · 17 commitsv0.1.51 · 2026-10-03 · 11 commitsv0.1.52 · 2026-10-03 · 13 commitsv0.1.53 · 2026-10-06 · 60 commitsv0.1.54 · 2026-10-08 · 13 commitsv0.1.55 · 2026-10-08 · 29 commitsv0.1.56 · 2026-10-09 · 12 commitsv0.1.57 · 2026-10-09 · 21 commitsv0.1.58 · 2026-10-09 · 14 commitsv0.1.59-beta.2 · 2026-10-10 · prereleasev0.1.59-beta.3 · 2026-10-10 · prereleasev0.1.59-beta.4 · 2026-10-10 · prereleasev0.1.59 · 2026-10-11 · 17 commitsv0.1.60 · 2026-10-11 · 90 commitsv0.1.61 · 2026-10-14 · 148 commitsv0.1.62 · 2026-10-18 · 22 commitsv0.1.63 · 2026-10-27 · 361 commitsv0.1.0v0.1.63 · latest

release · height = commits since the previous tag (log scale)prereleasetag only, no public releaselatest

Hover a line for its version, date and commit count; click to open the release.

VersionBeijing timeStatusMain changes
v0.1.632026-10-27ReleasedChange course mid-reply, answers as they are written, undo a turn
Every new terminal command asks first by default, and scheduled tasks run no commands; a message typed during a reply reaches it at its next step; answers stream as they are written (OpenAI-compatible providers, Claude, Gemini); up to four consecutive reads run at once; "Undo this turn" puts the working folder back from a checkpoint; files come back as attachments, including on the iPhone; the model's own search first; fewer tools in each request; MCP tools asked about by what they do; a $2 per-task cap; the desktop app's server locked down; the expert-era code removed (12,514 fewer product lines than 0.1.62). The notes list what was moved to 0.1.64 and two pre-release bars that were missed.
v0.1.622026-10-18ReleasedLong commands finish, fewer "needs your check", MCP tools can't break a chat
A fix release. A terminal command runs as long as it asked for (2 minutes by default, at most 10) and a time limit keeps what it printed; "needs your check" only when an action may have left a trace outside the Mac and Arslan cannot tell; MCP tool descriptions are reshaped for Anthropic, OpenAI-compatible providers and Gemini, and a tool a provider still refuses is left out instead of failing the reply; osascript and shortcuts run in the common terminal spellings ask every time; writing a file never follows a link out of the working folder. Corrects two lines of 0.1.61's notes.
v0.1.612026-10-14ReleasedOne approval card everywhere, MCP servers that start themselves, Claude works again
Models from Claude Opus 4.7 on no longer get a 400 error for a temperature setting they refuse (it is sent only to models that accept it, and removed and remembered when a provider stops accepting an optional setting); every MCP server you switched on starts in the background (at most four at a time) with one state the Capabilities page and the model both read; the same approval card in the chat, the Inbox, the island and on the iPhone, with Once or For this task, In this conversation, Always and No… with a note, 30 minutes to answer, and running out is not a no; iPhone files saved on the Mac under Inbox/iPhone and live conversations on the phone; web pages readable behind a fake-IP proxy, with page reads that name themselves.
v0.1.602026-10-11ReleasedHands v2: your Mac apps, used more like you would
A picture of the one window Hands works in (never the whole screen, never saved); batches, direct menu choices and apps opened in the background; one card for several apps and trusted apps allowed for good, while deleting, sending, paying and submitting still ask every time; borrowing the front for about a second and visiting another desktop (both can be turned off), and taking over the screen for up to 30 minutes only with your yes; a new first run; unpairing on the iPhone removes it on the Mac too.
v0.1.592026-10-11ReleasedA chat that keeps up, answers in your language, work that counts as done
A long answer is not "Interrupted" and a second message waits its turn; Arslan answers in the language you write in; background work that did the job counts as done (folders checked on disk, files read counted); "Desktop, Documents, Downloads: Off" closes them to commands too; an approval asked while Arslan is not in front waits instead of failing the turn. Preceded by four betas.
v0.1.582026-10-09ReleasedReplies you can read, files you can open, any model you like
What Arslan did is one quiet line under the reply that opens into plain sentences; files it writes come back as cards; one workbench (Task, Files, Browser) with a reader for text, tables, images, PDF and sandboxed HTML; any model of any provider per conversation, with Set as default and a context ring; a level is no longer cleared past open checkpoints.
v0.1.572026-10-09ReleasedCapabilities that grow
When a step needs something Arslan cannot do, it searches the MCP Registry, GitHub and reviewed skill libraries, asks on a card, installs it pinned with no install-time code, scans it, runs it in a deny-by-default sandbox, tests it once and retries the step. Discover, "Arslan found" and a dossier per capability on the Capabilities page; GitHub skills pinned to a commit.
v0.1.562026-10-09ReleasedProjects in two layers
A board (Idea, Shaping, Doing, Done) for every project and levels for each, drafted from the project type without a model. Arslan ticks checkpoints from files, what you said and finished runs; clearing a level is a proposal, and Done and Dropped are yours. Hands runs each request at most once and reports "sent, not confirmed" honestly.
v0.1.552026-10-08ReleasedOne look for everything that asks
Every card, dialog and notice shares one design; cards wait in one queue; ⌘⏎ allows, Esc declines. Island v2 answers cards except risky steps, which open in Arslan. The Inbox holds what waits for your decision; Settings in seven places; Memory as one page; Capabilities as switches; Arslan knows its own version.
v0.1.542026-10-08ReleasedReady for Arslan for iPhone, and a stricter terminal gate
Touch ID approvals count; the same iPhone pairing again replaces its old entry; removing a phone deletes what waits for it in iCloud; Settings shows Connecting…/Connected. The terminal gate checks every command in a chain, needs "don't ask again" for every matched rule, gives the access token only to a caller that has it, and keeps background jobs sandboxed. The iPhone app is still coming to the App Store.
v0.1.532026-10-06ReleasedArslan Hands, and the Mac side of iPhone
A separate macOS helper holds the Accessibility permission, reads AX trees and operates Mac apps; approval per app, sensitive actions confirmed each time, stoppable and traceable. The Mac side of Bridge ships; the iPhone app is marked as coming soon.
v0.1.522026-10-03ReleasedMemory really reaches the model
"About you" and "Arslan's notes about this Mac" stay in context; past conversations are searchable; corrections and successful detours become learned practices you can undo; ordinary sandboxed scripts interrupt less.
v0.1.512026-10-03ReleasedThe notch Island and the command sandbox
The Island shows tasks and progress in the notch, shows the plan on hover, expands when confirmation is needed, and says when work is done or stopped; run_command by default limits writes to the working folder/temp/caches and protects key files, with leaving needing your approval.
v0.1.502026-10-03ReleasedContext and delivery wrap-up
Added agent_status, update_plan, alternative routes after failures, corrections to what it claims it can do, link checking and more reliable file saving; redid Activity statistics.
v0.1.492026-10-02ReleasedExecution layer on the native tool protocol
OpenAI-compatible providers use native tool calling and canonical tool history; reasoning is preserved, truncation handled, retries back off, a streaming watchdog runs, long output is saved in full, and search was fixed.
v0.1.482026-10-01ReleasedOne agent loop
Removed the front router and the expert execution path, so every message goes straight to the agent; unified the default working folder ~/Arslan, the terminal, automatic browser setup, Activity and task workspaces.
v0.1.472026-09-30ReleasedProactive inbox
Deterministic detection of unfinished tasks, failed schedules and changed web pages or folders produces Inbox suggestions with evidence; background work starts only when you choose to act.
v0.1.462026-09-30ReleasedCompact settings, no wasted spend
The Settings sidebar stays fixed and the right side scrolls on its own; stopped the background self-evolution spending of retired experts.
v0.1.452026-09-30ReleasedBrowser and Mac actions
Added actions in its own browser, Shortcuts and AppleScript; actions run in the background; websites and Shortcuts each ask the first time, AppleScript asks every time.
v0.1.442026-09-30ReleasedOne Arslan
Stopped creating and dispatching experts; old experts can become Skills, and scheduled tasks all run as Arslan; a read-only failure no longer ends the whole turn, and side effects with unknown results are not repeated.
v0.1.432026-09-30ReleasedWrap up when the budget is reached
Background jobs get their own budget; at the soft limit, searching ends and what exists is delivered; only real runaway hits the hard stop.
v0.1.422026-09-28ReleasedBackground work, free conversation
Long work runs as a background job while chat continues; shows done-when criteria, progress and Stop, with up to 3 jobs in parallel; interruptions are clearly marked after a restart.
v0.1.412026-09-27ReleasedResident in the menu bar
Closing the window now hides it and keeps working; added desktop notifications and keep-awake while running; source review became an off-by-default suggestion after saving.
v0.1.402026-09-26ReleasedStable everyday research assistant
Brought project memory, task continuation, document-source evidence and workspaces into a stable release; backups are verified automatically before migrations, with native manual backup.
v0.1.392026-09-14 (tag date)Tag only; no public release nowFocused desktop settings (tag)
Improved the desktop Settings layout, side navigation and API key display. The version tag exists, but the current public release list has no such version; the date is the tag date.
v0.1.382026-09-14ReleasedReliability foundations
Strengthened the default-deny Python sandbox, a separate compute runtime, persistent Runs/checkpoints/deliverables, a shared execution budget, recipes and recovery; added native Gemini tool calling; added non-loopback HTTP/WebSocket authentication guards and restricted MCP environments.
v0.1.372026-09-03ReleasedHalf-duplex voice conversation
Added voice conversation that sends when you finish speaking; fixed microphone permission and read-aloud language choice.
v0.1.362026-09-02ReleasedPush to talk
Added hold-to-record voice input.
v0.1.352026-09-02ReleasedProxy discovery in the installed app
Fixed how the packaged app finds the system proxy.
v0.1.342026-09-01ReleasedMigration fix
Fixed migrations recorded as successful that had not actually completed.
v0.1.332026-09-01ReleasedConnection status from real checks
Unified provider connection status and checks it at launch, so green means it actually works.
v0.1.322026-08-31ReleasedVoice output
Replies can be read aloud.
v0.1.312026-08-31ReleasedCommon folders readable by default
Widened the default read-only scope to Desktop, Documents and Downloads.
v0.1.302026-08-31ReleasedExplaining provider refusals
Turns refusals from all three provider types into understandable reasons.
v0.1.292026-08-23ReleasedSearch what you already have
Search existing resources; the memory inbox supports the keyboard and prevents accidental deletion; the FastAPI/Starlette version cap was lifted after verification.
v0.1.282026-08-23ReleasedEquipment explains why it doesn't work
The equipment screen explains when a provider cannot call tools; fixed several page titles leaking i18n keys.
v0.1.272026-08-21ReleasedNetwork discovery and SSH
Added network discovery, remembered remote machines and SSH execution; every remote execution asks, and an aiosqlite concurrency error was correctly attributed.
v0.1.262026-08-21ReleasedScheduled work in plain language
Arslan can create scheduled tasks, list those it created, and keep a checklist it re-reads on a cadence (off by default); added scheduling approval cards, output budgets and error explanations.
v0.1.252026-08-20ReleasedReading and writing the local workspace
Can read the workspace, and writes run after per-conversation approval; clearer Discover results.
v0.1.242026-08-20ReleasedExtension manifests and visible equipment
Recognises arslan.plugin.json; equipment is confirmed per MCP server, the configuration an extension's author provides is shown, and Arslan knows what it can actually do.
v0.1.232026-08-17ReleasedSwitching chats keeps work running
A turn journal and reattach replay keep a turn that is still answering; the packaged app can find npx.
v0.1.222026-08-16ReleasedOAuth and onboarding
Remote MCP OAuth, OpenRouter sign-in and four-step onboarding work end to end; update-check status is visible, and the desktop shell compiles on every push.
v0.1.212026-08-13ReleasedModel-slot UI and connection fixes
A usable interface for per-task model slots; fixed Anthropic tool-schema transport; supports self-hosted SearXNG within SSRF limits.
v0.1.202026-08-09ReleasedThree key states, search and model roles
Distinguishes keys that are not configured, decryptable or not decryptable, and stabilised the database salt migration; added keyless search fallback, search sources and errors, per-task model slots and showing which model answered.
v0.1.192026-08-04ReleasedLaunch animation and new conversations
A launch animation plays while the backend loads; launch opens a new conversation and clears unused empty ones; a blob URL fixes the launch video not playing in WebKit, and a packaging check was corrected.
v0.1.182026-08-03ReleasedUI regressions and settings
Fixed overlays closing by themselves; improved the LLM layout, system theme, OCR choice and related hints.
v0.1.172026-08-03ReleasedContinuity of old expert sandbox sessions
Returning to the main conversation no longer destroys an expert trial session; old experts can read the main conversation's last turn as read-only background.
v0.1.162026-08-03ReleasedConsistent overlays
Fixed menus overflowing and how they close; stopped edit popovers silently losing changes; fixed port probing.
v0.1.152026-08-02ReleasedConversations survive restarts
Conversations are kept across restarts; the old expert-invitation state survives answering a clarification.
v0.1.142026-08-01ReleasedSettings regrouped
Regrouped Settings and brought spending controls together in one place.
v0.1.132026-08-01ReleasedEmpty states, i18n, budget and truncation fixes
Fixed Skill-injection truncation and concurrent evolution attempts refunding each other's budget; completed empty states and backend internationalisation.
v0.1.122026-07-31ReleasedCapabilities and UI filled in
Added a capability-fit matrix, Brain search, OCR language choice and a Playwright MCP preset; bounded the cost of evolution evaluations and added a packaged liveness check.
v0.1.112026-07-29ReleasedSystem OCR fallback
When the vision model cannot read an image, macOS Vision OCR takes over; added system-language filtering and packaging acceptance.
v0.1.102026-07-28ReleasedKeep the model's error details
Passes the provider's original error body into the exception instead of a vague failure.
v0.1.92026-07-28ReleasedImages and scanned PDFs
Images flow through input, all three provider types, chat and the old expert path; scanned PDFs are rasterised within a page limit for vision models to read.
v0.1.82026-07-28ReleasedChat and window regressions
Fixed three regressions: blank chat, a dead drop zone and a window that could not be dragged.
v0.1.72026-07-28ReleasedWebSocket hotfix for the packaged app
Fixed the installed app being unable to connect over WebSocket, which made chat unusable; a file that was not actually stored no longer shows as fed in.
v0.1.62026-07-27ReleasedLayout and accessibility
Fixed Skills overflow, duplicate headings, accessible names and layout details.
v0.1.52026-07-27ReleasedNon-blocking update notice
Added a corner update pill instead of a blocking dialog; fixed the state before an update check.
v0.1.42026-07-27ReleasedDesktop first, and internationalisation
Install guidance became desktop-first; the backend emits stable translation keys, the remaining hard-coded Chinese was cleaned up, and regression guards were added.
v0.1.32026-07-27ReleasedInstall and floating window
Blocked running directly from the DMG; fixed the floating window's title bar and drag permission.
v0.1.22026-07-27ReleasedAuto-update actually checks
Added the missing update-check call; the update pipeline existed before but never started a check.
v0.1.12026-07-27ReleasedFirst-launch chat and drafts
Fixed sending on first launch, kept chat drafts, tidied the Settings layout and completed Brain/Diagnostics localisation.
v0.1.02026-07-27ReleasedFirst desktop installer
Packaged the earlier Meta-Agent, memory, tools and research capabilities as a Tauri desktop app, with the Python backend shipped as a PyInstaller sidecar; set up signing, notarisation, installer acceptance and auto-update artifacts.

The 0.1.40 preview branch: a number existing does not mean it was released

The public prereleases are beta.3, beta.5 and beta.6. beta.1 and beta.2 keep their candidate tags and records; beta.4 was not released separately, and its changes went into beta.5. The six numbers are not six public previews.

PreviewBeijing timeEvidence statusChanges and scope
v0.1.40-beta.1 record— (no public release date)Unreleased candidate; tag keptPersonal-companion preview candidate
Project/memory/task workspaces, an adjustable side panel, read-only public web pages and constrained file input. The beta.2 notes state beta.1 was not released; its cloud build was cancelled over an AnyIO advisory; the tag is kept.
v0.1.40-beta.2 record— (no public release date)Unreleased candidate; tag keptSecurity-patch candidate, blocked by CI
Fixed the pinned AnyIO version; the signed build and 65 fresh-install checks passed, but CI on the same tag did not, and the notes say DO NOT PUBLISH. A successful package cannot stand in for CI on the same commit.
v0.1.40-beta.32026-09-20Public prereleaseFirst currently visible public preview
Brought together workspaces, read-only browsing, file sources, six-language resources and security fixes; releases began requiring exact-source CI to pass before signing. Not yet the finished companion v1.2.
v0.1.40-beta.4 record— (no public release date)Not released separately; notes onlyIdentity and trust clarifications, not released on their own
Settings show the client version actually running and the stable/preview channel; a short identity summary can be copied. beta.5 explicitly includes the unpublished beta.4 changes, so no separate install is needed.
v0.1.40-beta.52026-09-23Public prereleaseResearch evidence and continuity
Read / unread / partly-read labels, constrained source links, extraction of Word's current text with disclosure of tracked changes, tolerance of damaged trajectories; a limited model pilot ran, but not a full 12/12 pass.
v0.1.40-beta.62026-09-23Public prereleaseVisual and app-identity polish
Updated icon, with a choice of frosted amber or plain monochrome; fixed WebKit launch corners and typesetting. A manually installed preview; the stable auto-update channel was left as it was.

beta.2 is an example of a release state that needs recording on its own: the signed build and installer checks succeeded, but CI on that tag did not pass, and the notes say not to publish it. beta.3 then added the gate "the commit to release must have successful CI". beta.5 states it includes the unpublished beta.4 changes, so beta.4 need not be installed first.

How to trace a change

Released entries link to their release; v0.1.39 links to its version tag. Summaries combine the official release notes with the commits between neighbouring tags, and never describe something that exists only in a planning document as delivered. To check a specific implementation, pin to that version's tag or release commit, rather than reading the past through the ever-changing main branch.

Some release documents in the repository still carry pre-build HOLD or draft notes. They show how a release went at the time, but should not be used alone to judge whether something is publicly released; check the public release status and the final verification record together. "No public release now" here describes what is visible at the time of checking; the release documents show that v0.1.39 and beta.2 once had unpublished release drafts, which are no longer in the release list.

all public releases; version tags; beta.3's same-commit CI verification; beta.5 on the unpublished beta.4; the current release.

17 / Baseline and source entry pointsEvery claim in this document leads back to evidence

This document describes the implementation at a fixed release, cross-checked against the public release notes, Git tags and history, the release commit's source, architecture decisions and public acceptance material. Earlier designs are used to explain how things evolved; behaviour that later code replaced is marked as history. The author's independent, AI-assisted way of working is the author's own statement.

ItemBaseline used
Releasev0.1.63 · 9bbb6ad653b48f8c1e63c649b6f7217f074d6dad
Published2026-10-27 22:26:31 Beijing time (UTC+8); 2026-10-27 14:26:31 UTC
Checked as of2026-10-27. Dates are in Beijing time by default; Git implementation points and release publication dates are labelled separately.
After the releaseChanges on main after 9bbb6ad6 are not counted as part of v0.1.63 until a release ships them.
Version range64 consecutive tags, v0.1.0–v0.1.63; 63 currently public releases; 0.1.39 is marked separately as tag-only. The six 0.1.40 beta numbers and the four 0.1.59 betas, with their different release states, are listed too.

Find the implementation by question

  1. How did the project evolve? public releases; earliest traceable commit; One Arslan; the single loop.
  2. How does the program start working? desktop lifecycle; local service entry; agent entry; tool loop.
  3. How are models connected? adapter; tool trajectory; execution budget.
  4. How are tasks persisted and resumed? task contracts; state, persistence, attempt fencing and checkpoints; acceptance; voiding approvals and quarantining tasks after a backup restore.
  5. How is memory used and erased? personal context; material retrieval; practices; memory persistence; versioned migrations.
  6. How do capabilities extend? built-in executors; plugin manifest; MCP; read-only workers.
  7. Who enforces permissions? terminal policy; command sandbox; execution and fallback; Hands helper.
  8. How does the phone connect? Bridge protocol; Bridge lifecycle; event and approval mapping.
  9. How is it evaluated and released? 0.1.51 real-task comparison; 0.1.52 acceptance; installer acceptance; release workflow.
  10. Open-source origins and feedback: licence; third-party attribution; security reporting policy; project issues.

This document describes a specific release and the limits of what can be checked about it. For later versions, read the matching release and source as well; architectural direction, design intent, test coverage and actual delivery are stated separately, so you can judge where each claim applies.

Arslan technical docs · based on v0.1.63 (source 9bbb6ad653b4), checked 2026-10-27. Updated with each release; the source files are in the repository's docs-site/ folder.