Changelog
Newest first. Each section says what changed and links to the guide that describes the behaviour as it is today; the guides themselves describe only the present. Every release also has GitHub release notes listing its pull requests.
- A script reaches a tool by its own name. The bridge keys each tool by its
camelCased name, so
await tools.read_file({ … })in anarchmax_evalorarchmax_runscript, the spelling a model writes, wasundefinedand died withTypeError: not a function. A tool’s own name now resolves to the same function as its camelCased key, governed and evented the same way; the listing still names only the camelCased keys, and a hook’stoolsis unchanged. See the code interpreter.
- Breaking: a hook reaches a governed mount only where its workflow grants it.
A hook runs on runtime authority, and the mount rule used to skip that authority
entirely. One mount table serves a session and every child it dispatches, so a
caller’s
beforeorafterscript could read a mount only a child grants, and write it where the host served it writable. A hook is now held to its workflow’s grants, taken across every state rather than the active one. It reads a governed mount thatallow_alwaysor any state’sallownames, and is otherwise refused withmount.not-allowed. It writes one only where a grant of the workflow asksaccess: read_write, and anaccess: readinallow_alwaysnarrows any mount for it, so the refusal ismount.read-only. A state’s ownallow,forbidandaccess: readstill do not bind a hook. Afilevalue is checked on the same authority, so a seed or return naming such a path is refused too. Migrate by granting the mount in the workflow:mounts.allow_always: [name], or{ mount: name, access: read_write }where a hook writes. See mount governance. - A workflow’s own
forbid_alwaysbinds its own hooks.mounts.forbid_alwaysandskills.forbid_alwaysreached a hook only as a caller’s inherited denial, so a top-level workflow’s hooks could read what its own lists denied. They now refuse the call withmount.forbiddenorskill.forbidden, as the docs already said. - Breaking: a sub-workflow’s unset returns reach its caller empty. A child that
finishes without setting some of its declared returns still completes. Its
caller’s
returnsnow carries every declared name, each unset one asnull, beside thenotenaming them. Before, an unset name was left out. A caller that tested for a key’s presence now tests fornull. See returns. - Breaking: a seed of a return lands unlocked. A name in both
requiresandreturnsarrived locked, so a child could never write its result. A seed of a return now lands unlocked, whether it is a call argument, a host’svariablesor a delivery naming one. A later turn’s seed leaves a value the session already holds in place. The agent may still lock a return withlock: trueonce it is final. A guard that reads a seeded return now reads a value the session can change until it is locked. - The prompt no longer reads as asking for returns up front. Every state shows
the trigger’s returns. It now adds that each is set once its final value is
known, that moving to another state does not need it, and that one is locked
only when it is final.
archmax_advancenever checked the returns. A state’s ownrequires:is still the only gate on leaving it. See a return is owed when the session finishes. - Disposing a session can no longer end the host’s process. A host that called
agent.dispose(sessionId)while a script’stools.*call was still pending freed the script’s QuickJS context under the call. A host does exactly that in afinallyaround a cancelledsend. When the call settled,@langchain/quickjswrote its result into the freed context and threw where nothing could catch it. The unhandled rejection ended the Node process, and every turn running in it. A turn that ended while a timed-out script still awaited a call did the same. Disposal now abandons the calls still pending and frees the context once every call made from it has settled.disposestays synchronous. See cancelling a turn. - A cancelled turn stops its scripts. The
signala host passes tosendreached a script’s tools but not the script, so a cancelled script ran on untiltimeoutMs, and a retry in itscatchcould start a sub-workflow under the cancelled turn. The signal now reaches everyarchmax_eval,archmax_runand hook script of the turn, in the session and in its child sessions. The evaluation ends with an error at once. Atools.*call still pending fails withthe turn was aborted, and any call the script makes after that is refused before it reaches the tool. A customsandboxRuntimereceives the signal as the third argument ofeval. See the code interpreter.
- A host tool knows which session its call is made in.
ToolContextcarriessessionId: inside a sub-workflow’s child it is the child’s own id, for a model’s call and a script’s alike. A session’s files are stored under<sessionId>/in the session store, and that address is now a documented contract, so a host that reads or writes them through its own store handle keys them byToolContext.sessionId. Keyed by the session the host sent the turn to, a child’s files landed in its caller’s folder, where the child’s ownread_fileandlsnever looked. The field is new and optional to read. See host tools. - Breaking: a child session’s id is its dispatch number. A child is
<parent>~<n>,ncounting the parent session’s dispatches from 1 across all its turns, and a grandchild is<parent>~<n>~<m>. The count is held in the parent’s checkpointed state. Before, the<state>:<workflow>:<ordinal>ordinal restarted with every agent, so a host building an agent per turn could hand a second dispatch the child an earlier one parked on, its thread and its folder. The model’s delegation calls in one batch are numbered in tool-call order, so a replayed node reaches the same children.dispatchIdis the child’s id below the root session (1,1~1) instead of a per-processsubwf-<n>.subRunIdentityis gone;dispatchIdOfandrootSessionIdOfjoin thespecsubpath.sessionIdRejectionrefuses a session id holding~in any shape but a child’s. A session parked on a child before the upgrade still resumes it, under the identity its park recorded. See what crosses, and what does not. - Breaking: a workflow writes a governed mount only where its own grants ask
to. A bare grant no longer takes the host’s write posture: a governed mount is
writable in a state when the host serves it writable, some grant of the same
workflow says
access: read_write, and no applicable grant saysaccess: read. A mount table serves a session and every child it dispatches, so a route served writable for one workflow let every other workflow in the tree write it through a bare grant. Now each workflow writes only what its own grants ask for. A spec that wrote a writable governed mount through bare grants alone addsaccess: read_writeto one of them. An ungoverned mount is unchanged. See read or read/write. - XML and JSON read as text whatever the store labels them.
read_filerefused a file its backend reported asapplication/xml, as a+xmlor+jsontype, or as JSON with acharsetparameter: it answered “is a binary file” and returned nothing. An object store labels a file by its registered type, so an Office document unpacked into the scratchpad could not be read, and so could not be edited as text. These types now read as text, for the model’sread_fileand a script’stools.readFile. Bytes and NUL-holding content are still refused. The defect dates from 0.4.0. See the workspace.
-
A
filesignature type. Arequiresorreturnsentry may declaretype: file. Its value is a path from the workspace root, such asscratchpad/attachments/invoice.pdforskills/billing/assets/template.pdf.signatureJsonSchemamaps it to{ type: "string", format: "file" }, so a host can render a file picker, andsignatureValueIssueschecks the path’s shape. Wherever afilevalue crosses a boundary (the start of a session,archmax_set_variables, completion, a delegation call), the runtime also checks that a file is there and that the session may read it. See the signature. -
Files cross a sub-workflow boundary as copies. A
fileargument is copied into the child’s ownscratchpad/attachments/, and the child is seeded with that path. Afilereturn is copied back into the caller’sscratchpad/attachments/under a name nothing there holds: a secondreport.pdfbecomesreport-1.pdf, including when concurrent calls return at once. See passing files. -
A child’s file space is its own, and the docs now say so. A child session has always resolved its paths inside its own session, but the sub-workflows guide said a child’s files came back to the caller, and the authoring skill said a child shared its parent’s session and run zone. Both now describe the separate session, and a test pins it. See what crosses, and what does not.
-
Hosts and the CLI hand a session files.
TurnInput.filesmaps a variable to{ name, content }. The runtime places the bytes in the session’sscratchpad/attachments/and seeds the variable with the path.archmax runtakes a repeatable--file <variable>=<path>built on it. See sessions. -
No reset just to stay put. The platform prompt told the agent when to call
archmax_resetbut never when not to, so it reset on follow-ups the current state could serve. In a workflow whose entry state is terminal, such as a one-state chat, “a terminal state never serves a follow-up” sent it back to the state it was already in on every new message. The prompt now says that a state that can serve the message answers it there, and that a reset never helps while the session is still in the state the conversation began in. It also lists a terminal entry state among the cases that take no reset, which is where the per-turn state block already stayed silent.archmax_reset’s description says the same in one clause. See waiting is not finishing. -
The title follows the conversation, not its latest message. The agent renamed sessions on follow-ups because the prompt asked for an update “when the task turns into something the old title no longer describes”, and the model judged that against the last message. The prompt now asks for a rename only when the conversation as a whole has moved to a different subject. A follow-up, a detail, a correction or a side question on the same subject keeps the title, and a rename names what the whole conversation is about. The
titlesentence inarchmax_set_variables’ description sets the same bar. A sub-workflow’s child still reads neither. See two reserved names. -
Prompt and description text only: the runtime accepts and refuses exactly what it did. A workspace that overrides the platform prompt with
.platform/system/GRAPH_STATE.mdkeeps its own text.
-
grep‘smax_countreaches the store. Deep Agents passes a search’s cap to the backend as a fourth argument, and the workspace’s wrappers took three, so neither the agent’smax_countnor Deep Agents’ default of 1,000 matches ever applied. The store returned every match, and only Deep Agents’ 80,000-character limit on the answer cut it short. The session zone, every mount and everymountSubtreemount now forward it, and a search addressed at asearchable: falsemount is capped even when its store ignores the cap. For hosts: a custom store’sgrepnow receivesmaxCount. See session storage. -
No more “could not prune” warning. The harness cut Deep Agents’
taskguidance out of the system prompt, but Deep Agents 1.13 never adds that guidance. The pruning found nothing and warned on every governed assembly. It is gone. The prompt is unchanged with one exception: the pruning also collapsed runs of three or more line breaks, so authored prose holding such a run now reaches the model as written. Theprompt-shapingevent still names the withheld tools. -
Deep Agents 1.13.4 or later. The
read_filedescription the harness rewrites exists only in 1.13, so the dependency floor is now^1.13.4. The harness also stopped passinggeneralPurposeAgent: false, which is not acreateDeepAgentparameter and never had an effect. As before, a plain agent is offered Deep Agents’tasktool with its general-purpose subagent, and a governed agent never is. SeeAgent, in detail. -
What Deep Agents adds per model is now documented and pinned by tests.
- A model with a Deep Agents harness profile gets that profile’s prompt suffix, and
createDeepAgenthas no per-agent opt-out. A Codex model reached throughinitChatModelis told to “bias to action”, against every human state. The env-configuredChatOpenAIis not affected: Deep Agents reads no model id from it. ChatAnthropicandChatBedrockConverseare cached by Deep Agents’ own middleware even withpromptCacheoff. OnChatAnthropic, Deep Agents also places a breakpoint on the volatile block.
See what the model reads and prompt caching.
- A model with a Deep Agents harness profile gets that profile’s prompt suffix, and
- A store without the protocol’s optional methods works again. Deep Agents makes
downloadFiles,uploadFilesanddeleteoptional and checks for them, and 0.4.0’s workspace defined all three and threw where a store had none. Deep Agents’ skills loader reads eachSKILL.mdthroughdownloadFileswhen the backend has one, so a plain agent over such a store found no skill. NowdownloadFilesis always present and reads a store without it throughreadRaw(exact bytes, orpermission_deniedfor text whose decoding replaced bytes).uploadFilesis present when the session store has it, and a mount without it refuses that file.deleteanswers an error. Nothing throws for want of a method. See raw bytes and deletion. remove_fileis the only way to delete a file. Because 0.4.0’s workspace could delete, Deep Agents registered its owndeletetool, which removes a folder and everything under it. A plain agent was shown it besideremove_file. Deep Agents is now handed the workspace withoutdelete, so that tool is neither shown nor able to delete.- The scratchpad opens only the always-on tools.
scratchpad/and the offload areas were open in every state to any tool that declared paths, ahead of the state’s grant, so declaring a granted host tool’s paths made it callable everywhere on scratchpad paths. They are now open only to the essential tools: the file tools, the file operations and youressentialTools. Any other tool needs its state’s grant there as anywhere, so declaring paths only narrows. For hosts: a non-essential host tool that ran on scratchpad paths without a grant now needs one. See file operations and path arguments. - A path argument may hold a list of paths. Each path of a list is governed with the argument’s
access, and a
paths:guard tests each one: a grant needs every path to match, a denial any. Before, a list was not governed by the path rules, and a guard read it as one comma-joined string, sopaths: [attachments/**]admitted["attachments/a.txt", "contracts/b.docx"]. A declared argument holding anything else (a number, an object, a list containing one) is refused (tool.path-argument). See host tools. - The binary type table is exported.
BINARY_MIME_TYPESandbinaryMimeTypeOfon@archmax-ai/harness/specare Deep Agents’ extensions whosewritecarries base64, so a host store honouring the convention uses the same table. - An out-of-turn tool context can be compared and logged. Its throwing
workspaceproperty is non-enumerable, so a deep-equality check or a serializer skips it; reading it still says why.
- File operations:
copy_file,move_file,remove_file. Three new always-on tools, in governed and plain agents alike, that work on one file without its content entering the conversation.copy_file({ source, destination, overwrite? })copies the bytes, text or binary (a.png, a.docx), creating the destination’s folders.move_filetakes the same arguments and writes the destination before removing the source.remove_file({ file_path })deletes a file. An existing destination is refused unlessoverwrite: true. Each answers with one line (Copied …,Moved …,Removed …, orError: …), and scripts reach them astools.copyFileand the rest. See file operations. - Path rules read each tool’s declared path arguments. The kernel used to apply its zone,
mount, skill and inherited-denial rules to a fixed list of file tools and their one
file_path/pathargument. Every tool now declares its path arguments and how a call uses each (read,list,search,write,remove,execute), and every path rule reads the declaration. A call naming several paths is refused when any one is, and the refusal names the argument. A removal is refused wherever a write is. Apaths:guard matches the tool’s declared path arguments, so{ tool: grep, paths: [...] }now guardsgrep‘spath(it used to match no call). Apaths:grant needs every path to match and apaths:denial needs any, soforbid: [{ tool: "*", paths: [secrets/**] }]refuses a copy out ofsecrets/and into it. The built-in tools’ verdicts are otherwise unchanged. Seemachine-spec. - Host tools are governed by their paths and handed the turn’s workspace. An
AgentToolDescriptormay declarepaths, or a host passestoolPathstocreateAgent, and its path arguments are then governed like the built-ins’. Before, a host tool reading a file was bound by no path rule. Its handler gets a second argument, aToolContextwhoseworkspaceis the turn’s workspace: the host’s mounts and the session zone, bound to the session, the instanceread_fileuses. The workspace now carriesdownloadFiles,uploadFilesanddeletethrough every layer, and a read-only mount refuses an upload and a delete as it refuses a write. See host tools. For hosts: a host tool namedcopy_file,move_fileorremove_fileis now refused at assembly (ReservedToolNameError), so drop your own. Declaring a built-in tool’s paths throwsToolPathsError. The three schemas add about 2,250 characters to every model call. - A sub-workflow child goes straight to its work. A one-state child spent two of its model
calls on bookkeeping: it named its session, because the platform prompt’s step 1 said it was not
optional, and it read its inputs with
archmax_get_variables, because its opening line told it to. A child’s title is never returned and describes a session nothing lists, and its caller already held every input. A child’s prompt now leaves out the “Name the run first” step and the paragraph ontitle, and itsarchmax_set_variablesdescription no longer asks for one. Its opening message lists its inputs’ values: strings JSON-quoted, numbers and booleans as written, up to 200 characters each and 1,000 in all. Any other input is named, witharchmax_get_variablesas the way to read it. A top-level session’s prompt and its cacheable prefix are byte-identical. For hosts: a child emits notitle-setunless its caller passed atitle, and theopeningnote’s text changes (its kind and shape do not). A workspace that overrides the platform prompt (.platform/system/GRAPH_STATE.md) should wrap its own title step in<!-- top-level-only -->/<!-- /top-level-only -->lines to get the same saving for its children. See what the child’s model reads. - A child short of its
returnscomes back with what it set and anote, and a caller’s successful retry recovers a failed delegation call. A sub-workflow child that finished without setting every declared return used to fail its caller outright, after a full run, although its prompt said it “does not complete until every one is set”; the caller had to run it again. Now it completes, with no second attempt and no extra model call, and the call’sreturnscarry the ones it did set plusnote: "Not all return variables were set by the sub-workflow: 'description' was not set.". The calling agent decides what to do about the gap. A typed return holding a value of another type still fails the call (invalid-return), and a top-level session finishing short of its returns is still rejected. The rendered signature tells each which applies. On the calling side, a failed delegation call no longer commits a rejection that nothing clears. Each failed call is recorded with its arguments and stays the calling state’s failure until a later successful call of the same workflow from the same state recovers it. One success recovers one failed call: the one whose arguments it repeats, else the oldest. A corrected retry therefore recovers, and a caller that retries and then finishes no longer routes throughon_error, but two parallel calls that both failed need two successes. Another workflow’s success, a sibling call in the same tool batch and a rejection with another cause recover nothing. The route names every outstanding failure with the arguments of its call. Two delegation calls failing in one tool batch no longer crash the turn withInvalidUpdateError. For hosts and authors:noteis now reserved, so a trigger whosereturnsdeclaresnotefails to load: rename it. A routed delegation failure’s reason now ends with(called with {…})naming the call’s arguments. A mocked sub-workflow that omits a declared return answers with thenotetoo, instead of failing. See whatreturnsenforces and failure is always closed. - Reads are text only.
read_fileon an image, audio, video, PDF or PowerPoint file used to hand the model the file as base64 in a multimodal block. An OpenAI-compatible tool message cannot carry that block, so the request failed or the model read base64. The bytes also landed in the session’s checkpoints, in a script’stools.readFileresult, and intool-resultpreviews. A binary file with an unknown extension (.zip,.docx,.bin) came back as decoded garbage. Both now return a notice instead:Error: '<path>' is a binary file (<type>, <size>) and was not read; read_file returns text files only.A file is binary by its type (as Deep Agents’read_filedecides it) or by a NUL byte in its content. Theread_filedescription the model is handed says so, in governed and plain agents alike. See the workspace. For hosts: a model with vision still sees the images it reads, once the host turns onimages(createAgent({ images: true })). The model’sread_fileof a PNG, JPEG, GIF or WebP then answers with one line, and the image is added to each later model request as ausermessage right after that batch of tool results. The image is read from the workspace for each request, so its bytes never enter the history, checkpoints, events or scripts. Earlier images stay attached (keep, default all), and one overmaxBytes(default 10 MB) or deleted since is named in text instead. Without the option, and for PDFs, audio and video either way, a multimodal model no longer receives the file throughread_file. Seeimages. - The agent ends a failed task with
archmax_raiseinstead of a reply. On GPT-6 Luna, an agent whose work could not be completed explained the failure in a reply, which ends the session as a success: the e2e case for an order-store outage raised in 0 of 4 runs on 0.3.1’s prompt. “When the work fails” is its own section of the platform prompt again; it bounds recovery to a retry or two and says to make the call in the same message as the explanation, and the tool description says that a reply which only explains the failure reports it as a success. The same case now raises in 9 of 10 runs, and no case that should complete raised. The prompt stays a fifth below 0.3.0’s (1,787 tokens). See ending a session as a failure. - The reference workspace asks for the follow-up call in the same message.
refund-requestandclarify(and the clarify examples in the sessions guide and the authoring skill) now say to callarchmax_advanceorarchmax_waitin the same message as the text for the person. With the wording “…, then call…”, GPT-6 Luna wrote the text and stopped. - The agent e2e reruns a failed case once. On a live model a case can miss a step it passes nine runs in ten; a failed case now runs once more on its own, and CI fails only when it fails again.
- A sub-workflow child’s streamed text is no longer reported as its caller’s. A child runs
inside its caller’s tool call and inherited the caller’s callbacks, the handler of the caller’s
own stream among them, so every token a child’s model produced was also emitted as an
agent-text-deltaof the calling state, with nosubWorkflowDispatchId. Two children answering at once interleaved word by word into the caller’s reply, and a caller’s turn that failed while a child streamed finalized the child’s text as the caller’spartialagent-text. A child is now invoked without its caller’s stream handlers: its text streams once, from its own session, tagged with the dispatch; the caller’s deltas are its own. Every other callback a host hands down, a tracer included, still sees the child’s runs. For hosts: the untagged duplicate of a child’s text is gone and the event shapes are unchanged; a filter that dropped untagged deltas while a dispatch was open can be deleted. See sub-workflow observability. - The platform prompt says that text alone ends the turn. GPT-6 Luna wrote the message a
state asked for and stopped, without the
archmax_advanceorarchmax_waitits instructions put after that message: a refund never reached its review, a clarifying question never parked. The prompt now says that a message without a tool call ends the turn and that the text and the call belong in the same message. A workspace that overrides the platform prompt (.platform/system/GRAPH_STATE.md) should add the rule. - The platform prompt is a fifth shorter (2,232 → 1,771 tokens,
o200k_base), with the same rules: repeated contrasts, generic tool-use explanation and rationale the model does not act on are gone, and waiting, deciding and failing are one list.
- The agent can end a session as a failure:
archmax_raise({ code, reason }). A new control tool, offered in every state (terminal ones included) and never declared, for work that cannot be completed: a system the work depends on keeps failing, the thing asked about does not exist.codeis the agent’s own short token (orders-unavailable),reasonsays what failed. The call must stand alone in its message, and it ends the session at once with the new statusfailed: no further model call, noon_errorroute, no hook, noreturnscheck, and a rejection still pending from earlier in the turn is replaced. A session that ends without it is a success, so the tool is for failure only; the tool description and the platform prompt tell the model to recover first and never to use it in place ofarchmax_wait,archmax_resetor a human state. Forbid it withtools.forbid_always: [archmax_raise](or a state’stools.forbid). A child session that raises fails its caller’s delegation call with the kindraised, naming the code and reason. The CLI reports✖ failedwith the code and exits 1; cases gain the structuralraisedassertion, andsucceeded: truefails on a raise. For hosts:Outcome.kindandWorkflowStatusgainfailed, which is finished; a switch over either that must be exhaustive needs the new member, and code that read “notrejected” as success must now check forfailedtoo. Every finished outcome the agent ended carriesexit(SessionExit):{ success: true }when completed,{ success: false, code, reason }when failed;SessionSummarycarries the same record for a failed session, and theraisedevent (state,code,reason,callId) precedes the closingstate-leave. A workspace that overrides the platform prompt (.platform/system/GRAPH_STATE.md) should add the new “When the work fails” section. See ending a session as a failure, how a session ended andon_error. - A script lists only its state’s tools. In
archmax_evalandarchmax_run,toolsused to list every tool the assembly registered, whatever the state allowed:Object.keys(tools)in a state with two tools printed all 120 of a connected MCP server’s. The calls were governed, the names were not.toolsnow lists exactly the tools the model’s own tool list carries in the state the script’s calls are governed against, re-scoped at the start of every evaluation, so after an advance the same REPL lists the next state’s tools. It stays one object, so a helper or a reference kept from an earlier call follows along. A tool the state does not offer is unlisted but still resolves by name, and calling it is refused with governance’s reason, as before. Lifecycle hooks are unchanged: they run on runtime authority and list every tool. For hosts and authors: nothing to change; a script that enumeratedtoolsnow sees fewer names, and every call is decided as before. See the code interpreter.
0.2.1 (unreleased)
Section titled “0.2.1 (unreleased)”- A parked session’s handoff works on providers that refuse tool history without tools. The
reply-only model call a park spends (the handoff into a human state or an
archmax_wait, a decision routed into another human state, a delegated child’s decision, a reply to a parked session) used to send an empty tool list beside a transcript full of tool calls, and every runtime note is one. Amazon Bedrock refuses that request (through LiteLLM:Bedrock doesn't support tool calling without tools= param specified), so every park failed there. The call is now handed the parked state’s tool list, like any other call in that state. It still cannot act: the kernel’stool.reply-onlyrule refuses every tool call it makes, and a model that calls one instead of writing its message leaves the park without that message and without a second model call. For hosts: the reply-only request now carries tool definitions; the public API and the events are unchanged. See a parked session can still talk.
- A failing tool is answered to the model, not thrown out of the turn. When a tool the agent
calls throws (a dropped remote connection, a flaky API, a filesystem error), the call is
answered with an error-status tool message carrying the error’s message. The session stays in
its state, the next model call reads the failure, and the answers to sibling calls of the same
step are kept. A tool failure is not a turn failure and never routes through
on_error. Behaviour change for hosts: a turn that used to reject fromagent.workflow.sendon a tool failure now continues. Read tool failures from thetool-resultevent, which still settleserrorwith the message as its preview. Parks and cancelled runs propagate as before, and a script’stools.*call still throws to the script. See tool governance andon_error. - Typed trigger signatures. A
requires/returnsentry may be{ name, type?, description? }beside the bare name, which stays valid and untyped.typeis one ofstring,integer,number,boolean,date,date-time,objectorarray. The runtime holds a typed entry at the turn boundary, atarchmax_set_variables, at completion and across a delegation, which gains the refusal kindinvalid-paramand the failure kindinvalid-return. The delegation tool’s schema carries each parameter’s type and description, and an argument that is exactly one${{…}}reference keeps the referenced value’s type. A trigger declaration may carry a caller-facingdescription, which leads the delegation tool’s description and never reaches the session’s own model.@archmax-ai/harness/spec(and the root) exportSIGNATURE_TYPES,normalizeSignature,signatureForTrigger,signatureJsonSchemaandsignatureValueIssues, so a host builds MCP tool schemas, start forms and request validation from the rule the runtime enforces. Additive: every existing spec loads and behaves as before. For hosts: a spec that uses an object entry does not load on an earlier version, so upgrade every host that reads a workspace to this release before its specs use typed entries, and read signature lists throughnormalizeSignaturerather than asstring[]. See typing a signature, the signature reference and building a host schema. - No warning for a model the runtime places no cache marker for. Assembly used to warn that
“prompt caching is inactive” on every turn for any model that is not Claude or Bedrock Nova,
including OpenAI, Gemini and OpenAI-compatible proxy aliases, which cache a stable prefix
automatically. That warning is gone. The
prompt-shapingevent still names the strategy (unsupported),cacheReadTokensonmodel-usageevents shows whether the provider caches, and the warning for a state whose model wants a different native caching mechanism stays. See prompt caching. - A rejected session fails the CLI command.
archmax run,decideanddeliverused to print✔ answerand exit 0 for a session that ended rejected, such as a start refused for a missing input. They now print✖ rejectedand the reason on stderr and exit 1, as the CLI reference always promised.OutcomeandDecideOutcomegainrejected, the reason, so a host reads why without reaching into the checkpoint. See the CLI reference.
0.1.0: first public release
Section titled “0.1.0: first public release”The first release of @archmax-ai/harness: a governed layer over LangChain Deep Agents.
- One
workflow.yamlper workflow is the enforced state machine: states, transitions, per-stateallow/forbidgovernance for tools, skills and mounts,before/afterhooks, budgets, error routing and triggers. See the workflow machine guide and the machine spec reference. - Human states and waits park a session durably;
decide,replyanddeliverresume it. See sessions. - Hooks and the sandbox: hook scripts,
archmax_evalandarchmax_runshare one QuickJS sandbox with a governed tool bridge. See the code interpreter guide. - Cases (
archmax test) and rubric grading. See testing and grading rubrics. - Sub-workflows, skills and triggers: see sub-workflows, skills and triggers.
- The
archmaxCLI (run,test,validate,sessions,decide,reply,deliver). See the CLI reference. - The library API:
createAgentand thesandbox,testing,cli,specandmessagessubpaths. See the public API reference.