AI agents that interact with the real world through tool calls pose fundamental safety challenges: agents might leak private information, cause unintended side effects, or be manipulated through prompt injection. To address these challenges, we propose to put the agent in a programming-language-based "safety harness": instead of calling tools directly, agents express their intentions as code in a capability-safe language: Scala 3 with capture checking. Capabilities are program variables that regulate access to effects and resources of interest. Scala's type system tracks capabilities statically, providing fine-grained control over what an agent can do. In particular, it enables local purity, the ability to enforce that sub-computations are side-effect-free, preventing information leakage when agents process classified data. We demonstrate that extensible agent safety harnesses can be built by leveraging a strong type system with tracked capabilities. Our experiments show that agents can generate capability-safe code with no significant loss in task performance, while the type system reliably prevents unsafe behaviors such as information leakage and malicious side effects.
Figures & tables
Figure 1. Overview of the tacit framework. The AI agent sends Scala code via MCP tool calls. The code is validated and compiled by the Scala 3 compiler with capture checking, then executed in a REPL. The capability safety library is a gateway for all interactions with the real world (files, processes, network, sub-agents).
Classified
Unclassified
Claude Sonnet 4.6
User + Inject. ( n=120 )
100%
100%
Malicious ( n=11 )
100%
81.8%
Total ( n=131 )
100%
98.5%
MiniMax M2.5
User + Inject. ( n=120 )
100%
97.5%
Malicious ( n=11 )
100%
27.3%
Total ( n=131 )
100%
91.6%
Table 1. The type system provides a model-independent security guarantee. Each cell reports the percentage of trials in which no secret information (e.g., an API key) appears in the agent’s output; higher is better. The rows separate two threat scenarios: User + Inject. (total 120 trials) pairs each of the 12 user tasks with all 10 injection attacks, while Malicious (total 11 trials) runs 5 direct exfiltration requests and 6 social-engineering variants without injection. Overall, there are 131 trials for each model and mode. The two columns compare classified mode, where secrets are wrapped in Classified [ String ] and enforced by the type system, against unclassified mode, where secrets are plain String with no type-based protection. In classified mode both models achieve 100% security; in unclassified mode, security degrades and varies across models.
Model
τ2 -Bench
SWE-Bench Lite
Airline
Retail
gpt-oss-120b
43.8%
53.3%
-
gpt-oss-120b
45.2%
57.0%
-
MiniMax M2.5
58.4%
75.1%
43.3%
MiniMax M2.5
61.0%
77.5%
41.7%
DeepSeek V3.2
57.8%
74.6%
-
Table 2. Writing type-safe Scala programs does not degrade agentic performance compared to standard tool calling. Each row pair compares a model using the standard tool-calling interface against the same model using our capability-safe harness ( ), which generates typed Scala programs instead of issuing tool calls. Across all models and benchmarks, the capability-safe configuration matches or slightly exceeds standard tool calling.
Appendix figures & tables4 assets
Supplementary material from the paper’s appendix.
Appendix
Figure 2. Data flow for processing classified documents. The type system acts as an information barrier between the untrusted zone (the cloud-hosted agent on the left) and the sensitive data zone (the program execution environment with classified storage and a local trusted LLM on the right). The agent generates code but cannot access classified content directly; any such attempt is blocked at compile time.
Model
Classified
Unclassified
Claude Sonnet 4.6
99.2%
83.3%
MiniMax M2.5
90.0%
88.3%
Appendix
Table 3. The type-based protection preserves utility even under adversarial injection. This table shows the utility rate for each model on the 120 user-task trials (12 user tasks × 10 injection attacks). Each cell reports the percentage of trials whose agent response passes correctness checks. The two columns compare classified mode, where secrets are wrapped in Classified [ String ] and enforced by the type system, against unclassified mode, where secrets are plain String with no type-based protection. Malicious tasks have no ground-truth answer and are excluded from this table.
Model
System
Banking
Workspace
Slack
Travel
Utility
Attack
Utility
Attack
Utility
Attack
Utility
Attack
gemini-2.5-pro
CaMeL
52.8%
0/144
53.8%
0/574
48.6%
0/105
0 1.4%
0/140
tacit
56.94%
0/144
50.54%
0/574
40.00%
0/105
64.29%
0/140
o4-mini-high
CaMeL
62.5%
1/144
81.4%
0/574
68.6%
0/105
74.3%
0/140
tacit
59.03%
0/144
52.86%
0/574
47.62%
0/105
68.57%
1/140
Appendix
Table 4. tacit on the four stock AgentDojo domains. Utility is the fraction of user tasks completed successfully; Attack is the number of injection trials in which the attacker’s goal was achieved over total trials. CaMeL numbers are taken from Debenedetti et al., 2025 ; tacit numbers are from our runs.
Model
Avg. Retries
Calls w/ Retries
airline
retail
airline
retail
DeepSeek V3.2
1.39
1.25
7.93%
1.57%
gpt-oss-120b
1.36
1.22
1.58%
0.32%
MiniMax M2.5
1.26
1.06
1.43%
0.33%
Appendix
Table 5. Compilation-retry statistics on τ2 -bench. “Avg. Consecutive Retries” is the mean number of retries on snippets that needed at least one retry; “Scala Calls Needing Retries” is the fraction of all generated snippets that triggered any retry.
Independent Researcher, Seattle, WA, USA · Independent Researcher, New York City, NY, USA · King Abdullah University of Science and Technology, Saudi Arabia