Arc 14 Catch-up: Agentic Solana An Arc 14 project built an AI agent on Solana with a wallet, tools, MCP server, policy engine, and autonomous workflow, enforcing a strict boundary where the model decides what to do but code decides what is allowed. The agent's private key remains in the application, never exposed to the model, and a hard cap on transfers prevents unauthorized spending. An AI agent with access to a wallet sounds powerful. It also sounds like it could be risky. A language model can misunderstand a request, choose the wrong tool, repeat an action, or confidently pursue a goal that should never have been allowed in the first place. Giving it unrestricted signing authority would turn those mistakes into transactions. Arc 14 was about building something more useful than that. Across Days 92–98, we gave an agent access to Solana data, then gradually introduced tools, a wallet, an MCP server, a policy engine and an autonomous workflow. At every stage, the same boundary held: The model could decide what it wanted to do. Code decided what it was allowed to do. The first challenge kept the stakes low. We built a small agent loop using Claude and a set of Solana devnet tools. The agent could answer natural-language questions such as: The model never connected to Solana directly. It did not construct arbitrary RPC requests or receive unrestricted network access. The application defined a small set of tools, described what each one did, and decided how to execute them. Claude could inspect those descriptions and choose which tool to call, but the surrounding application remained in control. The result still felt conversational. We asked a question in plain English. The agent selected a balance tool. The application called the RPC endpoint. The result went back to the model. The model explained it to us. Behind that interaction was a simple loop: The model supplied the judgement. The application supplied the capabilities. Each tool had four important parts: Those details mattered. A vague description could lead the model to choose the wrong tool. A loose schema could allow malformed or ambiguous requests. An implementation that trusted every argument could turn a model mistake into a system problem. Even the read-only tools needed boundaries. A balance tool should accept a valid Solana address. An account-inspection tool should return useful structured metadata. Errors should come back in a form the model could reason about without hiding the original detail from the developer. The prompt described how we wanted the agent to behave. The tool definitions established what it could actually ask the application to do. Day 93 raised the stakes. The agent received its own devnet wallet and two new abilities: That turned it from a read-only assistant into a system capable of changing on-chain state. The wallet belonged to the application, not to the model. Its private key remained inside the process that built and signed transactions. It was never inserted into the prompt, returned in a tool result or exposed through the model context. The model did not need the key. It only needed a tool that accepted a proposed recipient and amount. The application could then decide whether to build and sign the transaction. This separation matters because model context is not a safe place for secrets. Prompts, tool calls, logs and responses may be stored, inspected or passed between components. A private key placed there could be leaked accidentally or repeated in generated output. Keeping the key inside the signing layer meant the model could request an action without possessing the credential required to perform it. The agent could ask to send 0.05 SOL to an address. Only the application could turn that request into a signed transaction. We did not rely on the prompt to limit how much SOL the agent could send. The transfer tool enforced a hard cap. If the proposed amount exceeded that limit, the application rejected the request before building the transaction. We could have told the model never to send more than 0.1 SOL. It would probably follow that instruction most of the time. But a prompt can be misunderstood, overridden by conflicting instructions or applied inconsistently. A code-level check behaves differently. If the amount is too high, the transfer does not happen. It does not matter how persuasive the request is. It does not matter how confident the model sounds. It does not matter whether the proposed action appears to support the wider goal. The signing layer refuses. That is where rules involving money, permissions and irreversible actions belong. A per-transfer limit protects against one large transaction. It does not stop an agent from making many smaller ones. An agent limited to 0.1 SOL per transfer could still attempt ten transfers and spend 1 SOL overall. That showed why validation inside the transfer tool was only the beginning. The wider system also needed to decide: Those decisions belonged in a dedicated policy layer rather than being scattered through prompts and tool implementations. Day 94 moved the Solana tools into a Model Context Protocol server. Until then, the tools had lived inside one application. That worked, but it tied the capabilities to a particular agent implementation. An MCP server made them reusable. It exposed tools for tasks such as: Any compatible AI client could discover and use those tools through the same protocol. The server described what was available, accepted structured requests and returned structured results. This was similar to the role the IDL played in Arc 13. The IDL made a Solana program discoverable to software. MCP made agent-facing capabilities discoverable to AI clients. The useful part was not simply that another client could call the tools. The wallet key and safety rules stayed on the server. A new client did not receive direct access to either. It received the same controlled interface as every other client. That meant we could change the model, desktop client or agent framework without rebuilding the Solana integration or moving authority into the new client. MCP made tools easier to discover and reuse. It did not make every client trustworthy. A compatible client could request a tool call, but the server still had to validate it. The client might be misconfigured. The model might choose the wrong tool. A user might request an action outside the intended scope. A request might contain an invalid address or an excessive amount. The server remained responsible for: Tool results were untrusted too. On-chain text and metadata can be written by other people. A memo, token name or decoded account field returned to the model could contain instructions intended to influence its behaviour. The application could not treat that content as trusted guidance merely because it came back from a tool. Even if hostile data changed the model’s reasoning, the same policy rules still controlled what could be signed. A poisoned response could affect what the model proposed, but it could not expand the allowlist, raise the spending cap or reset the session budget. That carried forward the security lesson from Arc 12. Untrusted input is not limited to transaction accounts. It also includes the data an agent reads and brings into its context. Day 95 turned the simple transfer cap into a proper policy layer. A proposed transfer was denied unless it passed every rule. The policy checked: The application tracked cumulative spend outside the model context. The agent did not get to remember, reset or reinterpret its own remaining budget. The policy engine calculated that from application-owned session state. This was a deny-by-default design. The system did not search for a reason to reject the transfer. It required the transfer to satisfy every condition for approval. If any check failed, the transaction was not signed. A missing allowlist entry resulted in denial. A malformed address resulted in denial. An amount above the cap resulted in denial. An exhausted session budget resulted in denial. Unexpected input did not fall through into execution. It stopped. The model’s job was to reason about the goal. The policy engine’s job was to decide whether a proposed action fitted the rules. The agent might correctly calculate that a wallet was short by 0.4 SOL. That did not mean it had permission to transfer 0.4 SOL. The amount might exceed the per-transfer cap. The destination might not be allowlisted. The remaining session budget might be only 0.2 SOL. The correct outcome could therefore be a refusal even when the model’s reasoning was sound. That was an important property of the system. Its safety did not depend on the model making the right security decision every time. The model could be mistaken, overconfident, manipulated by the user or influenced by hostile tool output. The policy should produce the same result for the same proposed action and the same session state. Model behaviour was variable. Policy behaviour was meant to be predictable. A rejected action was not necessarily an error. Sometimes it was the system working exactly as designed. If the agent proposed sending funds to an address outside the allowlist, the policy engine denied the request and returned the reason. The agent could then decide what to do next. It might tell the user that the action was not permitted. It might ask for a different recipient. It might propose a smaller transfer. It might conclude that the goal could not be completed under the current limits. This was better than treating every denial as an exception that crashed the workflow. The policy result became information the agent could reason over. Approved actions could proceed to signing. Denied actions came back with a structured explanation. The agent could adapt to that decision, but it could not override it. Day 96 combined the agent loop, wallet tools and policy engine in a goal-driven workflow. The example goal was to make sure a savings wallet held at least a specified amount of SOL. The agent needed to: This was more than one tool call followed by an answer. The agent had to inspect the current state, decide whether action was needed, act within its limits and then check the result. That final verification mattered. A transaction signature shows that a transaction was submitted. It does not by itself prove that the intended goal was reached. The agent read the on-chain state again and compared it with the target. That closed the loop: Observe. Reason. Act. Verify. We tested the workflow under several conditions. In the normal case, the target wallet was below its minimum balance, the funding wallet had enough SOL, and the required transfer passed policy. The agent calculated the shortfall, requested the transfer and verified the new balance. In a constrained case, the goal was achievable only within the transfer cap and remaining session budget. The agent had to work within those limits rather than simply choosing the most direct action. In an impossible case, the policy or available funds made the target unreachable. The recipient might not be allowlisted. The shortfall might exceed the session budget. The funding wallet might not contain enough SOL. The agent could reason correctly and still be unable to complete the goal. That was not a failure of the system. The correct behaviour was to stop, explain the constraint and preserve the policy boundary. “Unable to proceed” is sometimes the right result. An agent loop needs a stopping condition. Without one, the model could continue calling tools, retrying failed actions or revisiting the same reasoning indefinitely. Arc 14 included explicit turn limits so the application could stop the workflow after a fixed number of steps. That protected against accidental loops and limited the amount of time, tokens and network activity one request could consume. A turn limit is a simple control, but it addresses a real feature of agent systems. The model decided what to do next. The application decided how long it was allowed to keep deciding. Other systems might also use time limits, tool-call limits or approval gates for particular actions. The exact control can vary. The important part is that the agent cannot grant itself unlimited time or unlimited attempts. Autonomous behaviour is hard to trust when all we can see is the final answer. The workflow therefore wrote structured logs for each important step. Those logs could record: This created a trace of how the system moved from the original goal to the result. The logs were useful for debugging, but their value went further. They showed whether the agent had interpreted the goal correctly. They revealed whether the policy engine had applied the expected rule. They made repeated actions visible. They provided evidence that a successful workflow had verified its work. They also made denials easier to explain. Without logs, we might know only that the transfer did not happen. With logs, we could see that the recipient was not on the allowlist or that the remaining budget was too low. Autonomous systems need this kind of evidence. A final response is not enough. Day 97 moved from building the system to explaining it. We wrote a public technical walkthrough covering: The most important distinction was between variable and invariant behaviour. The model’s reasoning was variable. It might choose different tools, phrase its explanation differently or take another path towards the same goal. The policy layer was invariant. The same proposed action and session state should produce the same approval decision regardless of how the model explained its intention. That made the architecture easier to understand. The agent was not safe because the model had been instructed to behave. It was safe because the model could reach the signing capability only through code that enforced fixed rules. The documentation needed to show exactly where that boundary lived. A public walkthrough of an agent system can easily become a long explanation of prompting. Prompts mattered, but they were only one part of this arc. The stronger material was in the tool contracts and policy rules. Which fields did a transfer request require? Which validation happened before policy? Which policy result came back after denial? Where did the private key live? Where was cumulative session spend stored? Which component built the transaction? How did the workflow confirm success? How were tool calls and policy decisions logged? How was untrusted tool output kept from bypassing the signing rules? Those details explained what the system could actually guarantee. A prompt described intended behaviour. A tool contract defined the action the model could request. The policy engine defined which requests could proceed. The signer controlled what reached the network. The logs showed what occurred. That was the full safety story. Day 98 turned the workflow into a short recorded demonstration. The demo began with a natural-language goal. The agent inspected balances, selected tools, proposed an action and passed it through policy. An approved transaction appeared on devnet and could be verified on-chain. But the demo also needed to show a denial. That was just as important as the successful transfer. A system that can move funds is easy to demonstrate. A system that can refuse an unsafe or disallowed request is more convincing. The denial showed that the policy engine remained in control even when the model proposed the action. The most useful demo therefore included: This made the boundaries visible rather than asking the audience to trust that they existed somewhere in the code. Arc 14 was not about handing an AI model a wallet and hoping for the best. It was about building an agent inside a system that retained control. By the end of the arc, we had learned how to: The central lesson was that autonomy and authority are not the same thing. The model can decide which tool might help. It can interpret a goal. It can calculate a shortfall. It can propose a transfer. It can explain a denial. But it does not hold the private key. It does not decide which recipients are permitted. It does not set its own spending cap. It does not remember or reset its own session budget. It does not override the policy engine. The model reasons. The code holds the authority. Day 92: Build a read-only Solana agent that answers questions through defined tools Day 93: Give the agent a devnet wallet while keeping its key and spending limits in code Day 94: Package the Solana tools as a reusable MCP server Day 95: Build a deny-by-default policy engine with allowlists and budgets Day 96: Run and verify an autonomous wallet-management workflow Day 97: Document the agent architecture, tool contracts, policy rules and run logs Day 98: Record a public demo showing both a successful action and a policy denial