Features
Everything Marlo checks before
your agent does something real.
Marlo evaluates a proposed action against the chain of events that produced it. Below is what that actually means in practice, feature by feature, including the ones that are more boring than the demo suggests.
The request becomes a record,
not a vibe.
A raw prompt is a poor authority document. Marlo converts each user request into a structured intent object with an explicit scope: the requested action, the resources it covers, the outputs permitted, whether external writes are allowed, whether money can move, whether anything can be deleted, and when the whole thing expires.
“Draft an email to John” produces authority to create a draft and no authority to send one. The agent can call email.create_draft. It cannot call email.send. That is not a heuristic, it is the record.
{
"intent_id": "intent-91a2",
"user_id": "user-104",
"requested_action": "summarize",
"authorized_resources": ["support_tickets"],
"permitted_outputs": ["chat_response"],
"external_writes_allowed": false,
"financial_actions_allowed": false,
"destructive_actions_allowed": false,
"expires_at": "2026-08-04T21:00:00Z"
}Extraction is layered. Explicit application controls first, deterministic parsing for known commands second, model-assisted extraction only for genuine ambiguity, human confirmation for high-risk intent, and conservative defaults whenever authority is unclear. We do not let the model we are policing decide what it was authorized to do.
All the pieces matter.
Every source of information that reaches the agent is labeled on arrival. Authority sources can expand what the agent is allowed to do. Data sources cannot, no matter how convincingly they are written.
| Source | Treatment |
|---|---|
| Direct authenticated user request | Authority |
| Organization policy | Trusted policy |
| System instruction | Trusted configuration |
| Approved internal database record | Trusted data, not authority |
| Tool result | Data, not authority |
| Email body | Untrusted content |
| Web page | Untrusted content |
| Support ticket | Untrusted content |
| Another agent's message | Delegated authority, scoped |
| MCP tool description | Trusted only from approved servers |
A spreadsheet can contain a customer address. That does not authorize sending data to it. An email can request a refund. That does not authorize the refund. A README can describe a shell command. That does not authorize a coding agent to run it.
Policies written as causes,
not as regex.
Marlo policies describe required and prohibited causal patterns. They are deterministic, which means they hold even though the agent underneath them is not.
SendEmail requires:
UserIntent authorizing "send"
AND recipient matches authorized recipient
AND attachments within authorized data scope
AND no expired approval
AND no prior successful execution of the same transaction
ExternalDataTransfer prohibited when:
sensitive data is in causal ancestry
AND destination is not approved
PrivilegedAction prohibited when:
sole authority ancestor is UntrustedToolResult
DeleteResource requires:
explicit destructive intent
AND approval bound to exact resource ID
AND approval bound to exact action hash
AND approval age under 10 minutes
FinancialTransaction prohibited when:
equivalent transaction already completed
AND current event is caused by retry or recoveryAn approval for one thing,
good for one thing.
A generic “Allow?” button stops meaning anything the moment the action changes after approval, or the approval gets reused, or several operations run under one click, or the recipient changes, or the agent executes it forty minutes later in a different context.
Marlo binds approval to the exact action: tool, target, parameters, content hash, execution count, and expiry. Change any of it and the approval is void.
Approved action: send_email
Approved recipient: jane@company.com
Approved attachments: report-q3.pdf
Approved content hash: SHA-256: 8b91...
Approved expiration: 10 minutes
Approved executions: 1Ten minutes, and then it is dead. That is a feature.
Blocking is the crudest thing
it can do.
Allow and deny is a bad interface for a production system. Most of the value sits in the decisions between them.
| Decision | Behavior |
|---|---|
| Allow | Forward the call unchanged |
| Deny | Prevent execution, return a controlled error |
| Require approval | Show a human the exact action and wait |
| Narrow | Strip or restrict unauthorized parameters |
| Substitute | Route to a safer read-only tool |
| Sandbox | Execute in an isolated environment |
| Quarantine result | Keep suspicious output out of model context |
| Rate limit | Break loops and runaway retries |
| Step up | Require stronger authentication first |
| Escalate | Notify security or operations |
| Observe only | Record the violation, block nothing |
Authorizing the request is
half the job.
Marlo compares what the agent proposed against what the tool actually did, wherever the downstream system exposes enough evidence to make the comparison.
Proposed: create one Jira ticket
Observed: created three
Proposed: read /project/docs
Observed: also accessed ~/.ssh
Proposed: refund $50 to customer 1842
Observed: refunded $500That comparison catches things no prompt-level control ever will: compromised MCP servers, schema mismatches, tool implementation bugs, silent duplicate operations, and side effects nobody documented.
Delegation without inheritance.
When one agent hands work to another, the second agent should not quietly acquire everything the first one could do. Marlo carries a constrained authority object across the handoff and tracks it as part of the causal chain.
Holds the user's authority. Can obtain quotes, can purchase, within the intent record it was given.
Receives a constrained authority object: obtain quotes, from two approved vendors, maximum amount zero, expires in thirty minutes. The purchase attempt has nothing behind it.
{
"delegated_by": "manager-agent",
"delegated_to": "procurement-agent",
"allowed_action": "obtain_quotes",
"purchase_allowed": false,
"maximum_amount": 0,
"allowed_vendors": ["approved-vendor-a", "approved-vendor-b"],
"expires_at": "2026-08-04T21:30:00Z"
}The procurement agent's purchase attempt traces back to a delegation that authorized collecting quotes and nothing else, so the purchase does not happen. This gets more important, fast, once no single log in your environment contains the whole decision path.
The Board.
Marlo's investigation view renders a session as what it actually is: a partially ordered graph of events with causal edges between them, not a list sorted by clock time. You can select any action and walk backward to whatever authorized it, or find out that nothing did.
Every link on the board carries an evidence grade, so you know which parts of the story are proven and which parts are reconstructed.
Blocked customer-data export. The action originated from content inside support ticket 1842, not from the user's instruction. The user authorized summarization only.
It is a case file, not a log file. That distinction is the entire point of building on a causal runtime instead of a database with timestamps in it.
Three grades, and we will
tell you which one you got.
“Proof” here does not mean proving what the model was thinking. Nobody can do that, and any vendor claiming otherwise is selling something. Marlo proves the observable chain around the action.
High-risk actions require high-grade evidence. An INFERRED chain is enough to open an investigation and not enough to authorize a production deletion. You configure that threshold. We ship conservative defaults.
Watch first. Enforce later.
Marlo starts in observe-only mode against real traffic. Nothing is blocked. Everything is recorded, evaluated, and reported: here is what would have been denied, here is what would have needed approval, here is the action your finance agent has been repeating on every retry since March.
Teams turn on enforcement policy by policy, usually starting with destructive and financial actions and leaving read paths in observe mode indefinitely. There is no version of this product that requires you to trust it on day one.
Three ways in.
Point your agent's MCP client at Marlo instead of at the server. Marlo handles tools/list, filters the tool inventory, intercepts tools/call, evaluates, forwards, and labels what comes back. No agent code changes.
For local MCP servers, launch through Marlo instead of directly:
mcp-guard -- some-mcp-serverMarlo starts the real server as a child process and proxies stdin and stdout. This matters more than it sounds, because local servers often run with the same privileges as the client and reach files, credentials, and shell commands.
For teams that control the agent code, drop authorization directly into the tool execution loop:
decision, err := marlo.Authorize(ctx, ProposedAction{
AgentID: agent.ID,
Tool: "send_email",
Parameters: params,
IntentID: session.IntentID,
})
switch decision.Result {
case Allow: return tool.Execute(params)
case RequireApproval: return approval.Request(decision)
case Deny: return decision.Explanation
}The proxy is the fastest way to get coverage. The SDK gives Marlo better context and better evidence grades, because your application can hand over intent and identity directly instead of having them inferred at the wire.
Deployment guidesSee it stop something.
Thirty minutes, a live agent, and a support ticket with something nasty in it.