Repository navigation
Upgrade if you point iad at a server you care about, open projects you did not write, give the
agent a service account, or run the MCP server over HTTP. In 1.7.0 every gate judges the server a
call runs on, a repository file cannot widen those limits until
you trust it, agent-supplied code runs as the account you chose or not at all, and passwords stay
out of places they have no reason to be. It also changes what several calls do, so read Breaking
changes before you upgrade. The terms below are defined in CONTEXT.md: operator file,
workspace file, launch settings, trusted file, effective policy, refusal, set-aside
item, service account and connection account.
Breaking changes
Each item says what changed and what to do. iris-agentic-dev check_config lists what iad set
aside and why, so run it first if a call that worked before is refused now.
Configuration files and trust
- A workspace file can no longer widen a limit until you trust it. A project's
.iris-agentic-dev.toml, a file named by--configand a file named byOBJECTSCRIPT_WORKSPACE
keep applying their connection, tool lists and anything that tightens. They stop applying
write_tools_enabled = true,destructive_tools_enabled = true, a per-serverwriteTools,
dataPolicy = "allow"or"redact"where your operator file is stricter,mcpTemplate = "dev"
or"test", anallowlist or role looser than yours, a kill allowlist,tls_verify = false,
anssh_host, a per-server service account and the compile-time gate switched off. Each one is
set aside and listed incheck_config. To apply them, read what the file loosens with
iris-agentic-dev trust --show, then runiris-agentic-dev trustin a terminal and type
trust. Or move the setting to~/.iris-agentic-dev.tomlor your launch settings. - Trust ends when the settings change. It covers the file's path, the loosening settings it
held when you trusted it, and the host, port and namespace of the server each applies to. Editing
other keys keeps trust. Changing a loosening setting, or the address it applies to, sets the
setting aside until you runtrustagain. A mid-session edit takes the same route. A trust
entry takes effect when iad next starts, andtrustsays to restart. ~/.iris-agentic-dev.tomlis always read. It is the operator file, never a workspace file,
and its[policy.*]sections apply in projects that have a file of their own. A project that
relied on its own file hiding your policies now gets the stricter of the two, part by part.
The walk up from the working directory stops before your home directory.
~/.iris-dev.tomlis no longer read; move anything in it to the operator file.- Some settings come only from the operator file or launch settings. Trust entries,
exec_as_os_user, the HTTP transport token,local_skillsandaudit_payloadsare set aside in
any workspace file, trusted or not. - Loosening settings from your own file stay off a server a project chose. An operator-file
or launch-settings setting that loosens a limit does not apply to a server an untrusted
workspace file named, unless that server is on this machine or is theIRIS_HOSTserver. - A workspace
[instance.<name>]that reuses a known server's name is ignored and reported.
The name is taken byservers.json, Server Manager or your operator file. - An unreadable or unparsable
.iris-agentic-dev.tomlstops iad from connecting. Every IRIS
tool answersCONFIG_INVALIDwith the file, line and column, and the gates stay closed.
Before, iad ignored the file and ran without its gates. - A server with no
mcpTemplategets the one its system mode implies. Live and Failover
servers refuse theExecute,CompileandSourceControlcategories, Test servers gettest,
and Development servers getdev. A mode iad cannot read is treated as closed for writes, never
as Development. DeclaremcpTemplate = "dev"for the server in your operator file if you want
the old behaviour. - Discovered servers have their system mode checked. Connections found through
IRIS_CONTAINER, the localhost scan, the Docker scan and.vscode/settings.jsonused to skip
the check. A remote one whose mode iad cannot read starts with writes off. - Policy values are read without regard to case, and a bad value fails closed.
"Live",
"Block"and"Subject"work. An unknownmcpTemplatemeanslive, an unknowndataPolicy
meansblock, an unknown role meanssubject, and one bad value narrows that field only. Before,
an unknown template meantdevand a bad fleet entry dropped every policy. - Roles are
workbenchandsubject.role = "workspace"still works through 1.7.x, with a
deprecation note incheck_config.role = "control-plane"is reported as unknown and treated
asworkbench.
Gates judge the server the call runs on
- A call with
server = "prod"is judged byprod's effective policy. That covers the
template, allowed categories, blocklist,dataPolicy, role, write gates and identity.
A server with no[policy.prod]section gets the strict defaults, not the default server's
policy. If you leaned on the default server's policy covering the others, write a section for
each. - Pool members start write-closed. A member is open for writes only when iad probes it as
Development or Test, or when you declare[policy.<name>] writeTools = true.
IRIS_WRITE_TOOLS_ENABLED=1and the top-levelwrite_tools_enabledopen the default server and
nothing else. A Server Manager profile whose user cannot read^%SYSstarts write-closed. - On the default server with an unknown system mode, the call's namespace decides.
iris_execute(namespace = "PROD")is refused where it ran before. allowlists apply to every IRIS tool.iris_admin, theiris_production*tools,
iris_docput andiris_globalset used to slip past them. Categories follow the action.liveandtesttemplates refuseiris_test,iris_coveragerun,iris_docwrites and
iris_generate_class.- The subject role covers every tool that can write. Fifteen or so tools gain a
confirm
parameter, andconfirmedis accepted everywhereconfirmis. Source-control writes on a
subject server are a fixed rule. iris_message_body'sdataPolicyargument can only narrow the server's policy. An
unknown value isINVALID_PARAMS.acknowledge_phianddata_policyare accepted for
acknowledgePhianddataPolicy.- The CLI runs the same gates as MCP.
tool,exec,compile,query,docandbatch
apply the write and destructive gates, your declarations, the unknown-parameter check and the
tool list. TheIRIS_ALLOW_PROD=1message is gone, andcompilewith no files no longer skips
everything. With writes off, these commands exit 1 with the gate's code. - The compile-on-save hook runs
iris-agentic-dev compile. It uploads the local file
before it compiles, and needs the binary onPATH. It no longer calls curl. - The benchmark runs four tools. It refuses any other tool, and refuses
server,
namespace,confirm,confirmedandforce_writablearguments. The refusal goes back to the
model as a tool error. The benchmark also readsIRIS_SCHEMEand--scheme. forcelifts nothing. A forced call is judged by what it runs.iris_compile force_writable = trueneeds both thecompileandadmincategories.- A resumed elicitation names its tool and server. A resume made for a different tool or
server is refused. A resume is gated as the write it performs, needs writes on, and uses the
originalcompileflag. The agent still answers the yes or no, not a person. - A source-control answer iad cannot act on stops the write.
iris_doc put,checkoutand
executeread the hook'sUserActionanswer from a labelled record, so chatter the hook prints
first no longer decides it. A page or program the hook asks to open (codes 2 to 5) is
SCM_NEEDS_UI, a code outside 0 to 7 isSCM_ACTION_UNKNOWN, an answer iad cannot read is
SCM_ACTION_UNPARSEABLE, an alert (6) isSCM_REJECTED, an error status from the hook is
SCM_ERROR, and a hook whose session cannot start isSCM_UNAVAILABLE. Before, each of these
read as "no checkout needed" and the document was written. - Tools that compile a scratch class are marked in the gate table. Reads over HTTP still
run through theIrisDevTmpscratch class, andreadOnlyHintstaystrueon them. Use
read_only = "strict"(below) on a server that must see no writes at all. read_only = "strict"closes the write and destructive gates. On every server, whatever
opens them:write_tools_enabled,destructive_tools_enabled, a[policy.<name>] writeTools,
IRIS_WRITE_TOOLS_ENABLEDorIRIS_ALLOW_PROD.iris_compileis a write, so strict refuses it
too. A refused call answersREAD_ONLY_STRICTwithgate_source: "read_only_strict".
Refusals and check_config
- Every refusal says its kind, and none tells the agent how to lift it. The response carries a
refusalobject with one ofacknowledgement_required,operator_limit,untrusted_setting
orfixed_rule. An operator limit or untrusted setting names the setting, and an untrusted
setting points at its set-aside item. Neither carries the operator file's path or the edit that
would lift it, because an agent that can write files makes any edit it reads. iad writes the remedy
to stderr once per setting, andiris-agentic-dev trust --showlists every remedy. Existing
error_codevalues stay. The role gate gainserror_code
(SUBJECT_CONFIRM_REQUIRED,SUBJECT_SCM_WRITE_BLOCKED), and its text moves tomessagewith
errorkept.POLICY_GATEgainssuccess: false. Compile-time gate refusals are error
results (isError: true). A privilege error from IRIS is an IRIS error, never a refusal. check_configgainsconfig_sources,effective_policy,compile_time_gate,set_aside,
identity,read_onlyandskills.credentials_withheldstays as a string alias through
1.7.x.config_watch_pathis never your home directory and can benull.write_tools_source
can beoperator_file.- An operator-file edit that loosens a limit waits for a restart. During a session, an edit
to~/.iris-agentic-dev.tomlthat tightens applies at once. One that loosens (a gate key,
read_only, any policy part, a trust entry,tls_verify = false, anssh_host, a service
account, an operator-only setting) applies when iad next starts. iad writes one line to stderr
per held setting, andcheck_configlists it underset_asidewith the lift "restart iad".
Deleting the operator file counts as loosening whatever it tightened. Before, every edit applied
at once. check_confignames a config file as the connection source only when it names a target. A
file with nohostorcontainerreportsconfig_file_named_no_target, and
config_file_warningsays when a workspace file exists but is not in use.- iad writes one line to stderr at startup when it set something aside. The line goes to
stderr only, so TSV and JSON output are unchanged.
Identity: where the agent's code runs
- With a service account set, every path runs the code as that account or refuses. That is
the HTTP generator,docker exec, NoPWS, SSH, the WebSocket terminal, a namedserver,
global_kill,iris_testandiris_coverage. Code that worked on the docker paths because the
container's OS user held%Allnow gets<PROTECT>. If the login guard cannot work on your
instance, setIAD_EXEC_AS_OS_USER=1orexec_as_os_user = truein the operator file. The
response then says"source": "os_user_opt_in". server = "qa"usesqa's own service account. A server with none is refused with
SERVICE_ACCOUNT_UNCONFIGURED_FOR_SERVERwhen a service account is set. Give the server a
service_username, inservers.json,iris_add_serveror an[instance.<name>]block in a
file you trust.- A named server never falls back to
IRIS_CONTAINER. It runs in its owncontainer, or the
call fails withNO_DOCKER_ROUTE_FOR_SERVER. - A refused service account stops the call. A 401, 403 or
<PROTECT>on the generator's
upload or compile returnsSERVICE_ACCOUNT_HTTP_DENIED. It no longer falls back todocker exec
as%All. iris_ws_open,global_kill,iris_testandiris_coverage run/startrun as the
service account. The WebSocket terminal needs the account to hold%Development:USE.
iris_coverage runalso needs%DB_IRISSYS:RWon the account, because the line monitor keeps
its state inIRISSYS; without it the tool answersCOVERAGE_PRIVILEGE_DENIED.- A WebSocket session ends when its identity changes. After the service account changes, the
open session is refused withSESSION_IDENTITY_CHANGED. - A terminal that asks for a user name is reported. A container whose terminal service needs
a password login answersDOCKER_TERMINAL_LOGIN_REQUIREDinstead of garbled output. - A withheld service account is a tool error.
SERVICE_ACCOUNT_WITHHELDreplaces an
invalid_requestprotocol error. - Responses gain
identity {user, source, path, verified, server}.auth_userand
service_account_envrepeat part of it and go away in 1.8.0. - iad sends an
IAD-ID:line first on the docker paths and strips it from the output. The
first line iad writes to the terminal is no longer the agent's. - SSH checks host keys. iad uses
BatchMode=yesandaccept-new, never
StrictHostKeyChecking=no. A host you have not connected to is recorded on first use. An
ssh_hostthat comes only from a workspace file needs trust.
Compile-time gate and code-edit block
- The compile-time gate refuses everything IRIS runs while compiling. That is
#execute,
##expression,##function, a#ifwhose operand calls code, braced parameter values,
projections outside%Projection.*,CodeMode = objectgeneratorandgenerator(quoted or
not), in.cls,.macand.incdocuments, in class XML put under a.clsname, and in
CREATE PROCEDURE,FUNCTION,METHODandTRIGGERbodies sent throughiris_query. A
construct inside a comment or string literal is not refused. Embedded&html<>,&js<>,
&sql(), Python bodies and XData no longer hide a generator that follows them. iris_compileby name reads the stored source first. It checks the class, the.inc
files it includes (not%system includes), and every member of a wildcard or package target.
A source it cannot read, for any reason other than "does not exist", is refused.iris_doc
putwithcompile=truechecks the stored includes as well.iris_compileaccepts only the flags and qualifiers that compile what you named. Flag
lettersc,d,k,uand-, and the qualifierscheckuptodate,display,
displayerror,displaylog,keepsource,quiet,silentandverbose, each with an
optionalnoprefix. Anything else isINVALID_PARAMS. Thebflag and the/subclassesand
/expandqualifiers (or an abbreviation of them) compiled stored subclasses the gate never read,
and their generators ran.e,pandhare refused too.- A compile that would also build an out-of-date superclass is refused. IRIS compiles a
class's stale superclasses along with it, and their generators run, but the gate reads only the
classes you named. A compile whose target has an out-of-date superclass outside%,Ens.,
EnsLib.,EnsPortal.,HS.,HSLIB.andHSMOD.answersCOMPILE_SUPERCLASS_STALEand
names it. Compile that superclass first; its own check runs then. A check that cannot run
answersSUPERCLASS_CHECK_FAILEDand compiles nothing. iris_generate_classstores what the model wrote. It used to compile whatever class already
had the name the model wrote, without saving the generated text and without the gate. It now
stores the text and compiles it the wayiris_docput withcompiledoes, so the gate, the
include check and the superclass check apply and a refusal is the tool's answer. The response
gainscompile_errors, a retry gets IRIS's compile errors, and a text with no class name is
INVALID_OUTPUT. It needs Atelier, so a docker-only connection answers
NOPWS_ATELIER_REQUIRED.CodeMode = expressionandcallare accepted. They run at call time. The gate refused
them before.- Turning the gate off needs trust.
compile_time_gate = false,[policy.<name>] compileTimeGate = falseorIAD_COMPILE_TIME_GATE=0work from the operator file, launch
settings or a trusted file.IAD_COMPILE_TIME_GATE=1always wins. Responses say
compile_time_gate: "off"when it is. - The code-edit block refuses
%Routine,$SYSTEM.OBJ.UnCompile, the routine-buffer commands
(ZLOAD,ZINSERT,ZREMOVE,ZSAVE),^rMAC,^rINCand the$SYSTEM.OBJroutine entry
points. It checks its own list of code-storage globals. Code that reads^Ens.Config*or
^IRIS.Temp*, or names a code global in a comment, is no longer refused. The block is a lexical
check that indirection can get past; its error text now says so and names the service account as
the boundary.
Credentials, TLS and the HTTP transport
mcp --transport httpon a non-loopback address will not start without a token. Set
IAD_HTTP_TOKEN, pass--auth-token-file, or put[transport] http_tokenin the operator
file. The token is at least 32 bytes, and clients sendAuthorization: Bearer <token>. The
server prints why and exits with status 2 if none is set. A configured token is enforced on
loopback too.- The HTTP transport checks
HostandOriginon every bind. It serveslocalhost,
127.0.0.1,[::1], the bound IP, and on0.0.0.0alsohost.docker.internal, IP literals and
each--allowed-host. A request with anOriginpasses only when it is same-origin or listed
with--allowed-origin. Anything else gets 403. Command-line and SDK clients send noOrigin
and are unaffected. IRIS_TLS_VERIFYandIRIS_INSECUREapply to the launch-settings connection only.
servers.jsonentries and[instance.*]blocks take their owntls_verify, Server Manager
profiles always validate, and a workspace file'stls_verify = falseapplies to that file's own
connection once you trust it. Addtls_verify = falseto the entry of a server with a
self-signed certificate. The audit client and theiris_searchclient validate certificates
too.- A configured scheme must be
httporhttps. Any other value makes that entry
INVALID_CONFIG. Environment credentials go to a workspace file's host only when scheme, host,
port and prefix all matchIRIS_HOST's. - Credentials are stored under iad's own keychain service, bound to the server's address.
iris_add_serverwrites to the keychain on macOS and Windows, where 1.6.x wrote plaintext.
servers.jsonis0600, and holds a password only on a host with no keychain, tied to the
address it was saved for.IAD_NO_KEYCHAIN=1skips the keychain. iris_add_serverrefuses a name that belongs to a Server Manager profile
(NAME_CONFLICT), andiris_remove_serverno longer deletes Server Manager keychain entries.
Re-registering a server at a different address drops its stored password
(password_cleared: true).- A
servers.jsonentry reads a Server Manager password only if you imported it and its address
still matches the profile. A.vscode/settings.jsonnamed server whose address differs from
your user-level profile gets no keychain password. - A
.vscode/settings.jsonserver with no readable password is not used. A named server with
no keychain entry, no keychain or no Server Manager profile, and a direct host with no
password, used to connect with the passwordSYS. They now build no connection, and
check_configlists<server>:password_missingunderset_aside. - A config file's container, namespace and account no longer reach the environment. iad used
to exportIRIS_CONTAINER,IRIS_NAMESPACE,IRIS_USERNAMEandIRIS_PASSWORDfrom the file
into its own process, so every child it started inherited them. It now passes them to the
connection directly. - Discovery sends no credentials until a listener identifies itself as IRIS. It looks for an
unauthenticated Atelier answer, Portal session cookies oriris_*metrics first. An instance
that hides all three is no longer found automatically; setIRIS_HOST. - Source-control hooks get an empty password, as they do from VS Code. A custom hook that logs
into another system with it stops working. - The local audit log stores sizes, not payloads.
code,query,args,contentand
valueare recorded as{redacted, chars, lines}, credential-named keys are scrubbed at any
depth, and the file is0600. Setaudit_payloads = truein the operator file, or
IAD_AUDIT_PAYLOADS=1, to keep full payloads. installreplaces a symlinked lock file instead of writing through it, writes the lock file
with proper escaping, and refuses dependency names with control characters.skills/skillsin the working directory is not served. Setlocal_skills = truein the
operator file orIAD_LOCAL_SKILLS=1to serve local copies of bundled skills. A served copy is
labelledoverrideand keeps the bundled tier.objectscript.connhonours"https": true. Skill recipes use thetoolCLI or
curl -K -with${IRIS_SCHEME}instead of a password on a command line.
SQL
- Read-mode
iris_queryrunsSELECT,WITHandEXPLAINonly.CALL,TUNE TABLE,
BUILD INDEX,PURGE,SET OPTION,FREEZE PLANS, DML, DDL,GRANT,REVOKEand a second
statement after;areSQL_WRITE_BLOCKED. A stored procedure can write anything, and IRIS
cannot tell iad whether one does, soCALLmoves tomode="write". RunTUNE TABLEand the
other maintenance statements throughiris_executewith$SYSTEM.SQL.Execute. iris_interop_queryreads message bodies only wheredataPolicyisallow. A call with
body_class,body_whereorbody_selectisDATA_POLICY_BLOCKEDotherwise.body_wheremust
be one filter: balanced parentheses, no;or comment outside a quoted string, and noSELECT,
INTO,UNION,INTERSECT,EXCEPTor write keyword. A quoted'a;b -- c'is now accepted.
Host files and the VS Code extension
- Tools read host files only inside the project directory.
iris_compilewith a file path,
kb indexandiris_symbols_localrefuse a path outside it, a path with.., and a symlink
that leaves it, withHOST_PATH_BLOCKEDand areason.iris_compilealso takes only.cls,
.mac,.incand.intfiles. The project directory isOBJECTSCRIPT_WORKSPACE, then
--workspace, then the directory holding the project's.iris-agentic-dev.toml, then the
working directory; the filesystem root and your home directory never count. With none, every
host path is refused.iris_symbols_localstops at 200,000 entries, 32 levels or 10 seconds and
says so intruncated_reason;kb indexreads at most 1,000 files. iris_testandiris_coveragetake class patterns only. A pattern with/,\or..,
such asMyApp/Tests, isINVALID_PARAMS. UseMyApp.TestsorMyApp.*. iad no longer points
^UnitTestRootat a shared/tmp/httest/. A root the instance already has is used as it is, so
tests staged under it run as before; an instance with none (a fresh container, where IRIS would
answerERROR #5007and run nothing) gets a root of its own for the run under<mgr>/Temp,
removed afterwards.- The VS Code extension verifies the server it downloads. Each extension build pins the
SHA-256 of the five release binaries, and a download or cached copy that does not match is
deleted and reported. Redirects go only to GitHub's release hosts over HTTPS, at most five hops,
and the body must matchContent-Lengthand stay under 256 MB. - The extension does less in a workspace you have not trusted.
iris-agentic-dev.serverPath
is machine-scoped, so a repository's settings cannot choose the program it runs. In an untrusted
workspace it reads no workspace settings and starts no server. A user-level
intersystems.serversentry wins over a workspace entry of the same name, and a server defined
only in the workspace never gets a password Server Manager stored.
Setup skills
- The setup skills bind published ports to
127.0.0.1and set one generated_SYSTEM
password.iris-agentic-dev-setup,nopws-setup,iris-vscode-objectscript,
irishealth-container,iris-cpf-mergeandiris-objectscript-eval, plus
docs/getting-started.md, the README and themcp --helptext, no longer un-expire every
account onSYS. The password goes to the container on stdin. The skills ask before they change
an existing container. If you rely on a container published on all interfaces, publish it
yourself.
Smaller behaviour changes
iris-agentic-dev queryTSV output escapes backslashes and control characters.iris_test_serverrefuses ahostthat is not a bare hostname or IP address, and does not
follow redirects.iris_info what=sa_schemarefuses names with dot segments.journal_searchwithholds values of blocklisted globals, and values of sensitive-named
globals needacknowledgePhi.iris_lookup_transfer importneeds the destructive tier, because it replaces the whole table.iris_coverageruns%UnitTest.TestProductionclasses the wayiris_testdoes.- On a server set to
redact,iris_message_bodyandstream_inspectreturn HL7 v2 redacted and
refuse other content withBODY_NOT_REDACTABLE.journal_searchstays refused underredact.
stream_inspectnow answers todataPolicylikeiris_message_bodydoes, and a refused stream
still reports its class, size and line count. iris_source_controlDisconnect and Reconnect need their own opt-in,
IRIS_SCM_ALLOW_DISCONNECT=1, and answerSCM_ADMIN_BLOCKEDwithout it.
IRIS_SCM_ALLOW_CHECKIN=1opens CheckIn only.check_configreports both underscm_opt_ins.iris_source_control executewithDiffor%Diffcounts as a read. Every other menu id is a
write.stream_inspectreturns at mostmax_charscharacters (default 20,000, at most 1,000,000),
with the wholesizeandtruncated. It no longer reportstype, because character and binary
streams share storage and the class it opened said nothing about the content. An id with no
stream isSTREAM_NOT_FOUND.iris_coveragerefuses withMONITOR_IN_USEwhen a line monitor is already running, instead
of stopping it;mode=stopstill stops it. A class with no executable lines reports
pct: null, not 0.0%, and a class the monitor does not know isCOVERAGE_ROUTINE_UNKNOWN.- Telemetry goes to
~/.iris-agentic-dev/telemetry/<session>.jsonlon every call. Writing it to
^IRISDEV("telemetry")as well is an operator opt-in,telemetry_sink = "iris"in the operator
file orIAD_TELEMETRY_SINK=iris, and then happens only on the call's server, with writes on,
outside strict read-only and not on a subject server. Before, every call on a writable default
server compiled a scratch class inUSERto record itself.telemetry_queryreads both places,
andcheck_configreportstelemetry_sink. iris_coverage mode=checkandiris_reload_poolcount as writes.checkstarts and stops the
line monitor, and a pool reload brings new servers into use.iris_reload_poolis in the
admincategory, likeiris_add_server.- Over the HTTP transport each MCP session has its own log store, pending dialogs and call
history, so one client can no longer read another's results withiris_get_logor
agent_history. - The MCP client's name and version reach the User-Agent and the IRIS audit event with only
letters, digits and._+-kept (others become_), capped at 64 and 32 characters, so a client
name cannot forge fields there.Visual Studio Codeshows asVisual_Studio_Code. iris_generate_classsendsANTHROPIC_API_KEYonly for a Claude model andOPENAI_API_KEY
only for any other model; it no longer sends one provider's key to the other's URL.- A response from IRIS larger than
IAD_MAX_RESPONSE_BYTES(default 128 MiB) is
RESPONSE_TOO_LARGE. journal_searchwalks the current journal from its newest record back, so records come newest
first and a record beforestartends the walk. From the oldest record, a search for anything
recent on a busy file stopped at the 200,000-record cap before reaching it.iris_global getreturns the stored value exactly. It used to drop\rand trim trailing
whitespace.
What's new
iris-agentic-dev trust. It shows a file's loosening settings and what each changes now,
asks you to typetrustat a terminal, and writes a trust entry to the operator file with your
comments intact. With no terminal it exits 2 and writes nothing.--showprints the same
without writing,--listshows each entry as trusted, lapsed or missing, and--revoke <path>
removes one. A terminal check proves there is a terminal, not that a person sits at it. An agent
that can start a pseudo-terminal can pass it, so the boundary that holds is your approval of the
shell command.
No MCP tool writes trust.- The operator file.
~/.iris-agentic-dev.tomlholds your policies, your trust entries and
the operator-only settings, and it applies wherever you work. - Effective policy.
check_configshows each server's policy and which file set each part.
The parts combine like this:mcpTemplatetakes the stricter of dev, test and live;allowis
the categories both files list;globalBlocklistis every pattern from both;dataPolicytakes
the strictest of block, redact and allow;irisAuditis on if either turns it on. A loosening
part from an untrusted file is dropped on its own, and the rest of that file's policy still
applies. A trusted file's part replaces the operator file's. - Refusal kinds.
acknowledgement_requiredis lifted by repeating the call withconfirmor
acknowledgePhi.operator_limitis lifted by changing a setting in the operator file, launch
settings or a trusted file.untrusted_settingis lifted by trusting the file.fixed_rule
is not lifted, such as the system blocklist. - The set-aside list.
check_configreturnsset_aside, one item per loosening setting from
an untrusted file, per operator-only setting found in a workspace file, per credential withheld
from a server, and per workspace server hidden by a known one. Each carries a stable id, its
source file, the reason and what lifts it. A refusal points at the id. A file whose trust has
lapsed lists what changed. - Service-account identity. Every tool that runs the agent's code returns
identity {user, source, path, verified, server}, whereuseris what IRIS reports as
$USERNAME.check_configshows the account each path would use, and makes one
GET /api/atelier/login probe that sends no code.iris_add_servertakesservice_username,
service_passwordandcontainer. On the docker paths, iad's first line drops the session to
the account with$SYSTEM.Security.Loginand ends the process if it cannot, so no password
reaches the terminal. - Strict read-only.
read_only = "strict"in any configuration file, orIAD_READ_ONLY=strict
in launch settings, hides and refuses every tool that uses a scratch class (37 tools), closes the
write and destructive gates on every server, answersREAD_ONLY_STRICT, and keeps telemetry
local. It only tightens, so a project file may set it. - HTTP transport token.
IAD_HTTP_TOKEN,--auth-token-fileand[transport] http_token,
with--allowed-hostand--allowed-origin. Seedocs/connecting.md. - Keychain storage, bound to the address. iad's own passwords sit under the
iris-agentic-devkeychain service, keyed by the server's endpoint, so a password saved for one
address is never sent to another. Files iad writes for secrets are created owner-only and
replaced atomically. - Per-connection TLS. One client builder serves every IRIS-bound request.
check_config
reports each server'stls_verifyand where it came from. - Passwords and search terms stay out of stored class source. Source-control calls, the
iris_docprobe,iris_production_diff, credential and user management, andjournal_search
pass them as call arguments instead of splicing them into the class iad stores on the server. - A bounded journal walk.
journal_searchreads records one at a time and stops at 200,000
records or 20 seconds. The answer carriesscanned,truncatedandtruncated_reason
(max_records,scan_caportime_cap). A search over a large journal returns what it found
withtruncated: trueand a hint to narrowstart,endorglobal_pattern.
Setting up a service account
The account needs a database layout that keeps application code read-only while leaving the
compiler somewhere to write. This is the layout that works on IRIS 2026.2:
- Keep application code, application data and a scratch area in three databases.
- Create the agent's namespace with the scratch database as its default routines database
and the application data database as its globals database. - Map the application's packages and routines into the namespace from the read-only code
database (Config.MapPackagesfor the package,Config.MapRoutinesfor its routines). - Set each database's resource with
SYS.Database.ResourceName. A new database starts as
%DB_%DEFAULT, and a role cannot tell the three apart until you change it. - Create a role with
%Development:USE(to reach/api/atelierand the terminal),
%DB_<code>:R,%DB_<data>:RW(orR) and%DB_<scratch>:RW. - Create the user with password authentication, that role and nothing else.
Saving a class in a mapped application package then fails (ERROR #5883 over Atelier, <PROTECT>
in a terminal), and a class in any other package lands in the scratch database, where it can do
no more than run as the service account. Two layouts fail, and iad reports them rather than
falling back. An account with only read access to the namespace's databases gets
SERVICE_ACCOUNT_HTTP_DENIED: Atelier refuses it before anything runs. A read-only code database
as the default routines database, with only IrisDevTmp mapped, uploads the scratch class and
then fails to compile it (ERROR #302). %Development:USE also opens the debugger and the rest
of Atelier to that account. iad never hands the agent its credentials, and the database
permission still blocks code edits. The login guard was measured on 2026.2 only; other versions
are untested.
Fixes
iris_global getreturns what was stored. Newlines,\rand trailing whitespace come back
exactly, for single values and subtrees.iris_global setkeeps a value on one source line, so a
newline invalueis stored, where before it broke the call.journal_searchno longer floodsmessages.log. It used%SYS.Journal.File:Search, which
logs one line per journal record it visits. On a busy instance one search wrote about 500,000
lines,messages.logrolled over and the container'siris-mainexited. The walk above logs
nothing. A search over a very large current journal also reports a timeout with the file size
and a narrower-window hint, notIRIS_UNREACHABLE.- Docker-only
iris_compilecompiles classes. It passedX.clsto$SYSTEM.OBJ.Compileand
failed withERROR #5351. It now strips the suffix, and passes documents toCompileList. - HL7 redaction holds across line endings and padding, and masks more.
iris_message_body
andstream_inspectredact whether segments end in\r,\nor\r\n, and when a segment
name has leading whitespace or the MLLP bytes\x0band\x1cbefore it, which used to let the
segment through unmasked. The mask now covers the identifying, contact and account fields of PID,
NK1, GT1, IN1 and IN2, and MSH-3;docs/tools.mdlists them. An empty field stays empty. PV1,
OBX, NTE and free text come back as stored, soredacted: truemeans the mask ran, not that the
message is de-identified. A near-missdataPolicyvalue such asAllowor"allow "never
returns the raw body. - BPL parsing has a depth limit. A deeply nested definition returns
BPL_TOO_DEEPand no
longer overflows the stack. iris_info what=sa_schemastays inside the Studio Assist path. A name with..segments is
refused.iris_test_serverdoes not take a URL as a host. A host with URL syntax is refused, and
redirects are not followed.iris-agentic-dev compile --flagsand TSV output escape what they print. A flag outside
the qualifier character set is refused before IRIS sees it, and every control byte, tab and
backslash round-trips through TSV.iris_coveragesharesiris_test's dispatch. It runs the same gated builder, so it follows
the same policy and finds%UnitTest.TestProductionclasses.iris_doc putwithcompile=truechecks the stored includes. It did not.- The SQL checks follow IRIS's lexing and no longer panic on non-ASCII text. They used byte
offsets on character text, so an accented letter could crash the check. One lexer now reads
literals (''doubling, a backslash is an ordinary character), delimited identifiers (""doubling)
and comments as IRIS does. TheDELETErow pre-check reads the table afterDELETE FROM, and a
statement led by a comment or a newline no longer skips themax_rows_affectedcheck. iris_table_infosplits a name on unquoted dots only.iris_coveragecounts lines that ran. It read the second element of a monitor row, which is
time, as the execution count, so a method that ran three lines reported one. It now monitors
every routine a class compiles to, not onlyPkg.Cls.1, and stops the monitor on every exit
path.iris_doc deletereports a delete IRIS refused. A locked document or a source-control
class that refuses inOnBeforeDeleteanswers HTTP 200 with the reason in the body. Single and
batch delete now returnDELETE_FAILEDor anerrorsentry instead ofsuccess: true.- A scratch class IRIS would not delete is reported. The cleanup read only the HTTP status, so
a refused delete left the class behind silently. The call's own result stands and carries a
warningsentry naming the class. - Lookup tables read empty values and report absent keys.
iris_lookup_manage getof a key
whose value is empty returns the empty value, notKEY_NOT_FOUND, anddeleteof an absent key
returnsKEY_NOT_FOUND, not IRIS's raw error text. iris_adminjournal_searchadvertisesacknowledgePhi. The schema left the parameter
out, so a client that validated arguments against it could not pass it.- Discovery reports what it skipped. A listener or container it did not send credentials to
appears underdiscovery.skipped.