Repository navigation
agenttalk 0.101.0
In short: the checks that decide whether a team's work is finished are stricter and say more
clearly what is missing. A seat that runs through the paid gateway can no longer satisfy any
required review, and an acknowledgement counts only while its author is still an active,
trusted seat: a finished result that rested on a reviewer who has since left now needs a
current reviewer to acknowledge it again (an acceptance close whose runner has left needs a
new attempt instead, as described below). The check before an irreversible step names
exactly what holds a request back and also stops on an outdated request. When agenttalk gateway start fails before the task-start request is accepted, the gateway keeps its stop
file; after that point the command says the gateway may be starting and how to check and
stop it. agenttalk scratch store sets up a throwaway store that commands in that shell
cannot leave (an ordinary folder alone does not: set it up with that command). The
documentation set gained a license, a technical overview, a page on roles, skills and
procedures, and operator procedures that put each safeguard before the step it protects; the
source package now ships the documents its own tests read. After upgrading, refresh the
installed skills as described under "Lead skills" below.
Added
-
The repository now carries its license. agenttalk has always been offered under the
MIT license (the package says so), but the repository had no license file, so GitHub
showed "no license", which by default means nobody may reuse the code.What you will notice: a
LICENSEfile with the MIT text and the line "Copyright (c) 2026
the agenttalk authors". GitHub now shows the MIT license, and the installed package
carries the same file. Nothing to do. -
A technical overview of how agenttalk is built (
docs/OVERVIEW.md). One new page
that explains how agenttalk is put together, for someone who will run or extend it.What you will notice: a new page,
docs/OVERVIEW.md. It shows the parts and how they talk
to each other, where the data lives under.agenttalk/(and what a reset keeps), a
message's life from send to reply, how the supervisor, wrapper and agent processes relate,
how the console and the paid gateway work, and which promises hold and which do not. It has
five diagrams, a short list of the words it uses, and an appendix of interfaces. Nothing in
the program changed, and no other page was changed.What you need to do: nothing.
Technical details: the page was checked against the code of release 0.100.0. It states the
Python versions the project's tests run on (3.10 to 3.13 on Linux, Windows and macOS). Where
an older page and the code disagreed, the page follows the code. -
One page says who does what in a team, which skill to use, and the steps the team follows.
This information was spread over several older pages, some of them out of date. It is
the first page of the smaller documentation set the operator asked for on 2026-10-10.What you will notice:
docs/ROLES-SKILLS-PROCEDURES.mddescribes each role, with what it
does, what it must never do and how it hands work on:- the operator and the liaison;
- the lead, developer, reviewer, tester, architect and challenger;
- a seat that works through the paid gateway.
It has one entry for every shipped skill, saying when to use it. It also gives current
step lists for handing out work, the review cycle, the merge gate, the release ritual, the
challenge, the acceptance pass, the lessons lookup, capturing lessons and scratch hygiene.
It states the merge rule the team now follows: one recorded GO on the exact head commit, from
a reviewer who did not build the change, with green CI and every automated P0, P1 and P2
comment triaged. It retires the older rule of two reviewers both re-approving the final commit. Its
facts were checked against the code of this version.What you need to do: nothing. The older pages stay until a later change moves them to an
archive.Technical details:
tests/test_roles_skills_doc.pyfails when a shipped skill has no
entry on the page, or the page names a skill that does not ship. A shipped skill is a
Claude Code file insrc/agenttalk/skills/claude/or a folder with aSKILL.mdin
src/agenttalk/skills/codex/orsrc/agenttalk/skills/devkit/. -
An operations guide brings runtime upgrades and rollback, supervisor hosting,
paid-gateway setup and spending limits, backups, recovery, logs and attended
cleanup together indocs/OPERATIONS.md. It distinguishes shipped commands
from steps that still need an operator or maintainer.
Fixed
-
The check before an irreversible step now says what is holding a request back. With both
--epochand--gates, a request that was still current but held only by a gate at HOLD was
reported as "pre-epoch", and the agent was told to ask for the request again. It now says
hold, names the gate and says not to act until it is GO (agenttalk gate check); a request
from before the latest epoch barrier is still reported as one, and when both apply, both are
named. The exit codes are unchanged (cmd_checkinsrc/agenttalk/cli.py). -
The source package now holds the documents its own tests read. The tests shipped in the
source package read the new roles page, two pages underdocs/ops/and the staging example
underdocs/examples/, but the package left those files out, so those tests failed when run
from it. The files are now included, with the new overview page, and a new test
(tests/test_sdist_docs.py) fails when a test names a file underdocs/that the package
leaves out. -
The check before an irreversible step now also stops on an outdated request (#448).
Before a merge, release, deploy or deletion, every seat is told to runagenttalk checkon
the request it is acting on. That check looked only for a withdrawn request and for gates on
HOLD. When the team had since moved to a new epoch (someone ranagenttalk barrier bump),
a request sent before that still passed, so a seat could act on instructions the team had
already set aside.What you will notice: the wrapped-turn prompt, the lead, listen and handoff skills (Claude
and Codex copies), the README and the agent, user and new-user manuals now give the check
with--epoch. A request sent before the latest epoch
barrier now makes the check exit 3, and the seat stops instead of going ahead. The seat needs
no epoch value of its own: the check compares the epoch stamped on the request when it was
sent with the current one.What you need to do: wrapped seats get the new prompt once their wrapper runs this version.
A seat run by hand keeps its older installed skill files, because the installer leaves a
changed file alone: preview withagenttalk install-skills --no-devkit --dry-run, back up
local edits, refresh on purpose withagenttalk install-skills --no-devkit --force, then
start a new session.Technical details:
src/agenttalk/wrapper/prompt.py(_DEFAULT_RULES, the "Before any
IRREVERSIBLE action" line), the six skill files,README.md,docs/AGENT-MANUAL.md,
docs/USER-MANUAL.mdanddocs/AGENTTALK-NEW-USER-MANUAL.md;check --epochis evaluated
by_epoch_verdictinsrc/agenttalk/cli.py. The newtests/test_irreversible_check_text.py
pins the full command in the prompt (both shells) and in every skill, that each Claude and
Codex pair says the same thing, and that every check example on those four pages names
--epoch;tests/test_skill_lint.pynow pins--epoch --gatesfor the lead skills. -
A seat that runs through the paid gateway can no longer give a required sign-off of a sign-off set (#447).
Such a seat (an "external worker", a cheaper outside model with a narrower level of trust) was
kept out of sign-off lists only at the moment the roster changed. If the sign-off policy or the
domain lists were edited by hand afterwards, a close could list that seat as a candidate and
count its sign-off.What you will notice:
- A close now leaves a paid gateway seat out when it works out who may sign, so its sign-off
is refused when given (close acksays why and records nothing, even with--override, and
close signoffs overrideis refused for such a seat too) and is never counted, even if the
record was edited to contain one. - An override recorded earlier now counts only while the seat that recorded it is still on the active
roster and is not a paid gateway seat, the same test a signature already passes. If that seat was
since reclassified as a paid gateway seat, retired, removed or renamed, the override is ignored as if
it had not been recorded: the close goes back to HOLD (and says so) until a trusted seat signs, which
then lets it through, or a current trusted lead records the override again. This deliberately turns
an older GO that rested on such an override (for example one by a lead who later retired) into a HOLD. - Other candidates are not affected: a trusted seat in the same role or group still satisfies
the sign-off. If the paid seat was the only candidate, the close stays on HOLD with the plain
reason "its only candidate(s) run through the paid gateway (external workers), which cannot
give a sign-off; add a trusted seat to the candidates (or override to escape)". close check(text and--json) andclose signoffs planname the seats that were left out
(signoff_exclusionsandexcluded_external_workersin the JSON); they show seat names only.- A close record whose stored sign-off state has the wrong shape (an override list that is not a table,
a sign-off set without a text id, an ack whose author is not text) now gives the normal "malformed
close record" HOLD instead of a crash. - The roster still refuses to name a paid seat as a candidate when you change it; that check
is kept.
What you need to do: nothing. To let the close pass, name a trusted seat as a candidate, or use
the existingclose signoffs override. - A close now leaves a paid gateway seat out when it works out who may sign, so its sign-off
-
A failed
agenttalk gateway startno longer leaves the gateway without its stop file (#456).
The command used to deletegateway.killfirst and only then check the ports and ask the task to run. If a
port was busy, or Windows or systemd refused to run the task (for example because it was disabled), the
command failed but the stop file was already gone, so a later logon or restart-on-failure retry could start a
gateway you had stopped. Now the ports are checked before anything is deleted. The stop file is deleted only
whilegateway startholds the gateway's startup lock, so a gateway launched at the same moment by another
trigger waits, and after a failed start finds the stop file back and refuses. If the task cannot be started
(or the command is interrupted), the stop file is put back exactly as it was; if you had none, none is
created. If putting it back fails (for example a permission error), the command says so plainly, shows both
errors and tells you to runagenttalk gateway stopto write it again; until you do, no stop file protects
the gateway. A gateway launched by that start waits for the startup lock up to 45 s (it used to wait 10 s), so a
slow but successful start request still succeeds. Once the task start request has succeeded, the stop file stays
removed: when the gateway then takes too long to become ready, when the command is interrupted, and when the
startup lock then fails to let go (the command exits with an error saying the start was requested and the
gateway may be starting, and to runagenttalk gateway statusand thenagenttalk gateway stopif it should
not run). If a failed start could not
put the stop file back and the lock also failed to let go, you still see that the gateway is not protected and
thatagenttalk gateway stopwrites the file again. -
A seat that runs through the paid gateway can satisfy no required close lens, and a malformed close record is reported before anything reads it.
The earlier fix (#447) kept such a seat (an "external worker") from giving a sign-off that belongs to a
sign-off set. A required lens you declare by hand (close open --lens security --allow security:gw) and the
lenses of an acceptance close were not covered, so the seat could still record, and have counted, an ack on them.What you will notice:
close ackfrom a paid gateway seat on any required lens (a sign-off set's, one you declared, an acceptance
lens) is refused with a plain message and records nothing, also with--override. Optional lenses are not
affected, because they feed no verdict.- An ack already stored on a required lens by such a seat (for example a hand-edited record) is ignored when
the close is counted, andclose checknames the lens and says why. A trusted seat's ack still satisfies it. - The acceptance checks name an acceptance lens ack from such a seat and do not accept it as independent. Only the
required acceptance lenses are checked; an optional note from such a seat is left alone. - An ack stored on a required lens now counts only while its author is, at check time, on the active roster and is
not a paid gateway seat: the same rule a sign-off signature and a stored override already follow. An ack from a
retired, removed, renamed-away or unknown author is ignored andclose checknames it. This deliberately turns
an older GO that rested on a since-retired trusted reviewer's ack into a HOLD, until a current trusted seat acks
the lens (or a current trusted lead records an override ack). A sign-off set's HOLD names the author whose
signature was ignored. Nothing about a seat's past is looked up, so a damaged or older roster cannot bring a paid
seat's ack back. - An acceptance close is the exception to that recovery: its frozen runner must give the accept, so if that runner
leaves the team before publication the close stays on HOLD, a lead's override ack does not help, and the close
needs a new attempt (a new close id with a plan that names a current runner; a published one continues with
close reopen --successor). A recovery command is tracked in issue #463. close checkno longer reads a malformed close record (for examplelens_acksthat is a string) before
reporting it: it gives the normal "malformed close record" HOLD instead of a crash.close publishstill
refuses a malformed record with its existing error (exit 2); it never publishes one.
What you need to do: nothing, except that an older close that rested on a paid seat's ack, or on the ack of a
seat that has since left the roster, is now on HOLD until a current trusted seat acks that lens. -
Trying a command no longer risks the live message store. A seat's shell inherits the
live store's location, and that outranks the folder it runs in. On 2026-10-08 a reviewer
who meant to try three setup commands in a throwaway store ran them against the live one;
nothing changed only by luck.What you will notice:
agenttalk scratch storemakes a throwaway store in your scratch folder and prints the
line that points a shell at it. The line clears the agenttalk settings the shell
inherited, and agenttalk's own commands in that shell then refuse every other store,
even by name. They also refuse a store whose.agenttalkfolder links elsewhere, cannot
be fully listed or holds a file with a second name outside that folder (a hard link),
also when a link there leads to such a file, andscratch,janitor,comprehension
and the assurance scan refuse to list, read, write or remove the folders they work on when
those lie outside,janitor --releaseand--release-reportincluded. Those two also
refuse a git remote they would read that is not a folder inside the fence, a network
address included, before git contacts it, and they hand git origin's folder exactly as
it was checked. They refuse outright a partial clone and a repository, here or origin,
that borrows objects from another folder through an alternates file, since git might
read anywhere for those. Origin is also refused when anything in it could send git to
another folder or address: a file in its place that names the repository, a commondir
file (which shares another repository's objects and refs), a worktrees folder, an
http-alternates file, a link or junction anywhere inside it, or settings that include
another settings file, fetch from a promisor remote or name a pack's web address. None
of these is read or followed: origin's settings file is only searched for those setting
names, so one that merely mentions them is refused too. A file in origin with a second
name (a hard link) is refused unless all its names lie inside the fence; the object
files a local clone shares with this repository pass, as both their names are inside.
Origin's address is read exactly as git gives it, spaces included, so a name with a
space at either end is not taken for another repository. A windowlessgateway run
(the gateway's scheduled task) refuses a log outside the fence, or one with a second
name outside it: it stops with exit 2 before making the log's folder, opening the log,
setting its permissions or rotating it.
A command can still check whether a path outside exists,
reading nothing from it:janitorfor the extra paths in its own settings, and any
command while it looks for the store in the folders above the current one. Git still
reads its own settings files and the metadata of the repository it runs in wherever
they live, and the programs git starts, such as hooks that repository installs, run as
they are.
It guards against reaching a real store by accident, not against a deliberate attacker:
it is a check inside agenttalk, not an operating-system sandbox. It assumes the files and
settings it checked stay as they are until it has used them: other programs, plain file
commands, links or hard links made after a command has checked, and remote or git
settings changed in that time are not stopped.agenttalk initrefuses to re-init a store the shell only inherited, even one that has
lost its settings file, and changes nothing. Before, it quietly showed that store's
settings. To re-init it on purpose, name it with--root.- A roster change other than
addfirst prints one line naming the store, when the store
came only from the inherited setting. - The test suite can no longer open a store outside pytest's temporary folder: not
through a folder that links outside, not while test modules are loaded, and not through
the programs it starts, even ones given an environment of their own or several started
at once. A test that tries fails, even when it expected an error, and a refusal outside
any test fails the run. A test run that a test starts stays inside the fence it was
started under: if its temporary folder would lie outside, also through a link in the
folders pytest picks for itself, it stops before making or emptying anything, and its
refusals are added to the report of the run that started it, so that run fails too.
The fence starts once pytest has loaded the suite's owntests/conftest.py, so what
that file and the modules it imports do while loading comes before it; today they only
load code.
What you need to do: nothing for your own bus commands (reply, send, progress, threads,
knowledge); they work exactly as before. To try commands, runagenttalk scratch store
first.Technical details:
src/agenttalk/store.py:AGENTTALK_STORE_FENCEnames a folder.check_store_fence
(called byStore.__init__) refuses a root outside it, or one whose.agenttalkfolder or
a link inside it resolves outside, or that has a folderos.scandircannot list (only a
missing path or a file counts as having nothing below it), or a file whoseos.lstat
st_nlinkis above 1 (a hard link) unless every one of its names is found in that folder,
matched byst_devandst_ino, looking again up to three times 50 ms apart so that
a lock another process is taking (_publish_text_no_replacelinks a private name to the
lock's name) is not refused; a symlink there to a file adds that file to the count with
no name of its own (_walk_names, comment 4236579569).check_second_namesapplies the
same rule to the files it is given, links followed: a file with several names passes only
when a walk of the whole fence finds all of them (known_only, which passes over places
that lead outside or cannot be listed). A path that cannot exist because a plain file
stands where one of its folders would be (NotADirectoryErroron Linux and macOS, a
missing path on Windows) counts as missing there, in the walk's look at the file a link
leads to, and inworktree_release._refuse_alternates. Under the fence
recovery.create_backupcopies instead of hard-linking (#423);check_folder_fencerefuses a folder outside it. All three
raiseStoreFenceError(aValueError, so the command exits 2) and append the refused
place to the file named byAGENTTALK_STORE_FENCE_REPORT, when that is set.src/agenttalk/checkpoint.py:log_hook_errorwrites nothing into a refused store.src/agenttalk/assurance.py:mainchecks the root, the run folder and the summary's final
place (a relative summary is placed in the run folder, aswrite_artifactdoes) before any
scan.src/agenttalk/cli.py:scratch store(--for,--task,--agents,--shell); its line keeps
AGENTTALK_PYand, when a test run set it,AGENTTALK_STORE_FENCE_REPORT;_inherited_root: the root came only fromAGENTTALK_ROOT, with no--rootand no
fence;- the
initrefusal (any.agenttalkfolder there, with or withoutconfig.json), and
_note_inherited_rootinroster; scratch root/store,janitor(and, in every mode,--releaseand
--release-reportincluded, its scratch, temp and.worktreesfolders, every
registered worktree and the checkout--releasenames, before anything is listed,
read, removed or asked of git) and_comprehension_rootcheck the fence first.src/agenttalk/worktree_release.py:check_fence, called bycmd_janitorin the
release modes under the fence before release runs. Origin must name a local folder
inside the fence: the address afterurl.<base>.insteadOf(git ls-remote --get-url),
relative to the repository, links followed, in each form git looks it up
(path/.git,path,path.git/.git,path.git), both as configured and as resolved.
A partial clone (aremote.<name>.promisorremote orextensions.partialClone) is
refused, and so is a non-emptyobjects/info/alternatesfile here or in origin
(_refuse_alternates, which never opens it; origin's is located first, links on the
way followed). Each form of origin that exists then goes through_refuse_elsewhere,
from gitrepository-layout(5) and git-config(1): it must be a folder (a file there is
a gitfile), with no non-emptyobjects/info/http-alternates, nocommondirand no
worktrees(_ELSEWHERE_FILES), nothing but plain files and folders at any depth
(_refuse_links,lstatonly: a symlink or any Windows reparse point is refused), and
none ofinclude,promisor,partialcloneorblobpackfileuri, in any case, in the
bytes of itsconfigorconfig.worktree(_ELSEWHERE_SETTINGS). Every plain file
_refuse_linksaccepts goes throughstore.check_second_names. The objects folder
and origin's address are read with_read_git_path, which takes Git's output as
bytes (_git(..., exact=True)) and removes only the final line end. A network or helper
address, a path starting with~, a file URL with a host or an escape, a.gitfile,
or settings git cannot give are refused withstore.refuse_unplaced.check_fence
returns origin's folder, resolved, andreleasepasses it tols-remoteandfetch
in place of the nameorigin(soremote.origin.*settings such asuploadpackno
longer apply); aurl.<base>.insteadOfrule that would rewrite it is refused.
src/agenttalk/gateway_run_log.py:route_missing_output_to_logcalls
_check_store_fencebefore it makes the log's folder. UnderAGENTTALK_STORE_FENCEthat
importsstoreand runscheck_folder_fenceand thencheck_second_nameson
gateway.logand its rotated copies (gateway.log.1,gateway.log.2); a refusal is reported and ends the run with
SystemExit(2), as there is no output stream to explain it on. Without the fence it
returns at once andstoreis not imported.- Tests:
tests/_store_fence.py, loaded bytests/conftest.py:configure(from a
trylastpytest_configure, before collection) sets the fence and the report and binds
subprocess.Popen's own arguments, so that every child, whatever itsenv(inherited,
keyword, positional, text or byte keys, empty or different), gets this run's fence and
report, always in an environment of its own; a fence that environment already holds is
kept only when it lies inside the one the child would get, so a narrower fence Popen
captured stays narrow throughos.posix_spawn.os.spawnve,os.spawnvpeand
os.posix_spawnare wrapped the same way.os.spawnv,os.spawnvp,os.systemand
multiprocessing.process.BaseProcess.startinherit: they put the settings into
os.environfor the launch, one at a time under a shared lock (_inheriting). A
per-test fixture fails a test that
added a refusal, andpytest_sessionfinishfails the run on any refusal, also from xdist
workers. A run started under a fence (a nested run or an xdist worker) checks its
--basetemp(without one, pytest's temporary root and itspytest-of-<user>and
pytest-of-unknownfolders, links followed) against that fence beforegetbasetemp
makes or empties it (_keep_inside), and checks the foldergetbasetempmade again
before it becomes the fence: a folder outside it, the fence itself or one that holds the
given report stops the run with exit code 4 and one report line. A
nested run that is not an xdist worker adds its refusals to the given report at its end.
tests/test_store_fence.pyandtests/test_probe_store.py. - The listen and lead skills, in the Claude and Codex copies, say how to try commands, and
so do the new-user manual and the agent manual.
-
The operations guide puts safeguards before the steps they protect, and three pages say
what the code does. Before a runtime switch, a rollback or a resume refreshes the
supervisor's scripts, the guide now has you disable its scheduled task and enable it again
only as the last step before the host starts, so it cannot start half-refreshed or before a
rolled-back gateway is in place; a restore disables it, and the paid gateway's task, from
the start and enables each only just before it starts; after a restore, the paid gateway
starts before any agent resumes; and a protected agent (the liaison or a lead) is started by
the protected restart request, because the supervisor never starts a stopped one on its own.
The roles page says what--work-headdoes on a review task (no stage requires it) and that
a published HOLD exits with code 3; the overview says the emergency release comes from the
liaison when there is one, and that a close can require captured knowledge (#444).
Changed
-
The README no longer lists a fixed problem as a known gap. It still said that the reply
instructions the wrapper gives a seat leave out the seat's name and that a team must work
around it. Since 0.100.0 the reply command the wrapper prints for each message carries
--from <seat>, and every other agenttalk command a seat runs takes its name from
AGENTTALK_SELF, which the wrapper sets, gateway-backed seats included (#354). Only the
missing completion record (#178) is still listed. The roadmap's rows for the lessons
look-up, the windowless gateway task, the worktree release, the domain-registry fix and the
dependency batch now say they are done. No behavior changed. -
Lead skills: renew timers before they expire, and watch named work without a fast timer.
The lead skills already told a lead to set a check-in timer (about every 30 minutes) and a
handover-note timer (about every 2 hours), but only to recreate them after a restart. In
Claude Code these timers belong to the session and lapse after some days, and nothing says so.What you will notice in both lead skills (Claude and Codex):
- The handover note now has one line per standing timer: its name, when it is next due, when
it expires (unverifiedif that cannot be read), the latest time to renew it and when it
last ran for certain. At each check-in and each restart the lead compares those lines with
what is really scheduled and renews a timer before it expires, by replacing it, never by
adding a second one. The text does not promise that a timer never lapses; a lead that was
paused for long should assume the timers are gone and rebuild them from the note. - A new short section, "Watching named work", for work that needs close attention. The lead
first agrees the next decision point, the signal that needs action, the longest acceptable
delay, and the watch's expiry and wake budget. It prefers the seat's own message, then a
background wait that ends when a message about that work arrives (best effort: the host
can end it, so the check-in stays the backstop), and uses a short
repeating timer only when there is nothing to wait for, as a temporary one that is
cancelled when the work ends or the budget is used up. A wrapped lead keeps its turns short,
never starts a second reader of its mailbox and never keeps a turn alive with a background
wait. A watch lets the lead notice sooner; it cannot steer a seat in the middle of a turn,
because a wrapped seat reads new messages between turns.
What you need to do: a copy of the skill that was installed earlier is not replaced by
agenttalk install-skillsalone; it reports such a file asskipped. There is no option
for a single skill, so the narrowest update is every agenttalk skill of one agent, without
the development skills. For Claude Code:agenttalk install-skills --claude-only --no-devkit --dry-runlists the files that differ (markedwould-skip), then the same command with
--forcereplaces them. For Codex use--codex-onlyinstead of--claude-only.--force
replaces every listed file with the bundled one, including a skill you edited yourself (not
only the lead skill), so copy any edit you want to keep first. Without--no-devkit, a
forced run also replaces the edited development skills, and it writes them for both Claude
Code and Codex even with--claude-only(the run uses the default skill folders unless you
name others). Then restart or re-open the lead so it reads the new text. - The handover note now has one line per standing timer: its name, when it is next due, when