8000 Core agent principles and the one-way / two-way door rule (M9.30, M9.38); hard-lined goals retire (M9.29) by adeelahmad · Pull Request #86 · adeelahmad/aloop · GitHub
[go: up one dir, main page]

Skip to content

Core agent principles and the one-way / two-way door rule (M9.30, M9.38); hard-lined goals retire (M9.29) - #86

Merged
adeelahmad merged 13 commits into
masterfrom
claude/m9-ethics-principles-goals-a6hfmm
Oct 20, 2026
Merged

adeelahmad merged 13 commits into
masterfrom
claude/m9-ethics-principles-goals-a6hfmm

Conversation

@adeelahmad
@adeelahmad adeelahmad commented Oct 20, 2026 •
Copy link
Copy Markdown
Owner

Requested by Adeel · project thread

Before: agents decided when to ask the human from prose alone. Nothing stopped an agent from pushing to main, publishing, deploying or emailing on its own. Nothing stopped it from interrupting the human with a question it could have decided itself. Separately, a goal that crossed an ethics hard line deadlocked the mission loop, because evaluate kept narrowing to it and goal derive refused.

After: every aloop agent carries seven core principles drawn from Amazon's Leadership Principles, with the door rule first.

  • A two-way door (reversible: a branch, a local edit, a test, a draft PR, a recorded default) is decided and recorded, never asked.
  • A one-way door (push to the default branch, force push, discarding local work, publish, deploy, external message, system change, destructive DB operation, visibility change, credentials, agent settings) is crossed only under the human's recorded authorization. Without one, the PreToolUse gate stops the action with ASK[door] <id>, and the agent asks one question with a recommended option.
  • Walls (switching a gate off, forging a human record, erasing the run log) are never crossed by an agent.
  • While a run is live, a question to the human must name a one-way door the gate stopped, so two-way questions are decided, not asked.

A hard-lined goal is now retired: the loop evaluates the whole mission again and derives the next gap. When a hard line holds every open gap, the loop asks the human.

How

  • principles.md ## Core and principles show --core. session-start injects the block.
  • doors.json is the catalogue; anything unlisted is two-way. Each door has a grant (standing, once, never, command), the reason it is one-way, and examples that doors check proves.
  • doors.py reads Bash with the scope gate's plugin/bin/_shellread.py (M9.38).
    • The gate follows wrappers, sh -c, eval, bash SCRIPT, find -exec, xargs and stdin (heredoc, here-string, echo|printf|cat |, a pipe into ( … ), the last fd-0 redirect, /dev/stdin, a python that runs its stdin) into the command they feed.
    • It tracks the directory through cd, pushd/popd, git -C and $(git rev-parse --show-toplevel), and places operands and writes relative to the project or this worktree, so a ledger or run log reached through cd or a glob is still the ledger.
    • A branch created in the same command (git switch -c x && git push -u origin HEAD) is not the default branch; after ; it may be.
    • Nesting deeper than 8 fails closed, and so does a read that takes over 8 s.
  • The reader (third review): close() ends a $( … ) where bash does (here-strings, << inside $(( )), (( )), ${…}, $[…], # comments, case patterns, two heredocs on one line) and is memoized, so a deep nest stays linear; a heredoc's delimiter is its literal word; ]] glued to an operator ends a test; $((cmd)…) and ((cmd)…) fall back to a subshell as bash does. The scope gate gains 15 rows for these.
  • Authorizations are hash-chained human ledger rows, written by aloop mission doors authorize|revoke in the human's own terminal. An agent's shell is refused. A once-grant is bound to one action's sha256, lasts 24 h and is used up by its first crossing. A broken ledger opens nothing.
  • Ledger order: M.record writes the ledger row before its stream event, and ledger_problems reads the stream first, so no gate sees a half-written record. A revoke only the run log has (another branch) is compared with an authorization on the run log's clock, so a ledger time a fast clock pushed ahead cannot outrank it.
  • Live runs end when the plan's sprint closes or a new intent arrives.
  • M9.29: evaluate.active_goal skips a hard-lined goal. tick hands out EVALUATE and then DERIVE_GOAL for the next gap, or BLOCKED when every gap is held.
  • ROADMAP rows M9.29, M9.30 and M9.38 are done. M9.31–M9.37 and M9.47 (computed command text) are open follow-ups.

Evidence

🤖 Generated with Claude Code

https://claude.ai/code/session_012rsDo9GYzNx8tXucywZHnA

…edger

Every aloop agent carries seven core principles drawn from Amazon's
leadership principles, the door rule first: a two-way door (reversible)
is decided and recorded, never asked; a one-way door is crossed only
under the human's recorded authorization, else the agent stops with one
question and a recommended option.

- principles.md `## Core` + `principles show --core`, injected by
  session-start in an aloop project (after the resume context).
- doors.json: the catalogue of one-way doors and walls; anything
  unlisted is two-way.
- doors.py: classifies an action by program and arguments (wrappers,
  sh -c, redirects), reads human door.authorized/door.revoked ledger
  rows (standing, until, once bound to one action's sha256), records
  door.crossed, and runs as the PreToolUse hook (deny JSON, fails
  closed). A broken ledger opens nothing. While a run is live a
  question to the human must name a door the agent was denied.
- door.authorized/revoked are human ledger events; door.denied and
  door.crossed are gate-owned.

Ethics (M9.4): ethics.reviewed and ethics.blocked are written with
M.record and the verdict reads the ledger only, so a hard line holds on
a fresh checkout, a stream-only record decides nothing, and a broken
ledger blocks every verdict.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012rsDo9GYzNx8tXucywZHnA
Each finding is a test that failed before its fix:
- Bash is read as the shell reads it: comments, line continuations,
  heredoc bodies (data unless a shell or python runs them), xargs,
  find -exec and `echo ... | sh` are handled.
- A `when` condition reads the directory the command acts on
  (git -C DIR, cd DIR &&); an unresolved one counts.
- Settings and the ledger are matched by path suffix (~/.claude,
  a worktree's own copy).
- Dry runs, help pages and `command -v` are two-way.
- git -c core.hooksPath= is a wall; git config core.hooksPath is a
  one-way door.
- The newest decision by time counts, and run-log revokes close the
  door on every branch.
- A once-grant is crossed under a lock.
- A broken ledger is named in the ASK and blocks authorize.
- authorize refuses an agent's shell (CLAUDECODE).
- The question gate is live only once a sprint's plan is approved,
  for the lock's session.
- Slack's real send tools are one-way.
- Hard ethics lines that exist only in the run log still bind.

New ROADMAP items: M9.36 (authorize on the typed line, after M9.17),
M9.37 (stdin to data clients), M9.38 (one shell reader with M9.9).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012rsDo9GYzNx8tXucywZHnA
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012rsDo9GYzNx8tXucywZHnA
adeelahmad pushed a commit that referenced this pull request Oct 20, 2026
adeelahmad pushed a commit that referenced this pull request Oct 20, 2026
adeelahmad pushed a commit that referenced this pull request Oct 20, 2026
…87 M9.28-30, #74 M9.40-42, #86 M9.50+)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Aw6CiQcdpzdzZZXKf8WbUM
adeelahmad pushed a commit that referenced this pull request Oct 20, 2026
… (M9.30, M9.38)

The second adversarial review of the door gate found 26 real problems; each is
now a test that failed before its fix.

- The gate reads Bash with the scope gate's _shellread.py (M9.38). The reader
  now keeps each heredoc's body (Word.body), the operator that ends a command
  (Cmd.end) and heredoc bodies inside $( ... ), and reads long words in linear
  time. Stdin (a heredoc, here-string or echo/printf/cat pipe) reaches only the
  command it feeds; pushd/popd, sh -c, eval and fed shells keep the directory;
  nesting past 8 is unreadable and a read past 8 s fails closed.
- Catalogue: python is a forge only when it calls a writer; npm/pnpm/yarn -g
  only with an installing verb; the ledger only at docs/agents/mission/;
  xargs -I replaces its string; secret commands' dry runs and help are two-way;
  git push origin HEAD on the default branch asks; erasing the run log is a wall.
- State: M.record writes the ledger row before its stream event and
  ledger_problems reads the stream first; a record is dated no earlier than any
  row; a closed sprint or a new intent ends the live run; malformed run-log
  lines never crash the ethics check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012rsDo9GYzNx8tXucywZHnA
…r options

The third review found that the shared reader dropped the substitutions of an
array assignment (x=( $(npm publish) )), and that `time -p --` and `sudo -u
root --` hid the command. Also `sh -c -e STRING`, `env -S STRING`, `exec -a
NAME` and `sudo -s CMD` now find the command they run (each option set is per
wrapper, so a value-taking option no longer swallows the program).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012rsDo9GYzNx8tXucywZHnA
… into the door rule branch

ethics.py takes master's M.history reading (M9.24) and keeps this branch's
hardening: a run-log event whose data is not an object is skipped, and a
non-list words/gaps field is ignored, so a malformed line never crashes a
verdict. The ethics test follows master's wording for a broken ledger and a
disagreeing stream record (blocked, not asked).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012rsDo9GYzNx8tXucywZHnA
Before: a goal an ethics hard line held stayed the active goal. Every later
`mission evaluate` was narrowed to it, `goal derive` refused that eval, and
`goal close` refused the goal, so th
8000
e mission deadlocked.

`evaluate.active_goal` now skips a goal whose ethics verdict is hard (the
verdict's own rule), so the next eval covers the whole mission and derive
moves past it. `goal derive` on an eval narrowed to a retired goal says to
evaluate again instead of naming a close that cannot succeed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012rsDo9GYzNx8tXucywZHnA
…erive the next gap

tick hands out EVALUATE for an eval narrowed to a goal a hard line retired
(evaluate now skips it), then DERIVE_GOAL, which takes the next gap. When a
hard line holds every open gap, tick asks the human (BLOCKED) instead of
handing out a derive that would refuse.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012rsDo9GYzNx8tXucywZHnA
@adeelahmad adeelahmad changed the title Core agent principles and the one-way / two-way door rule (M9.30); ethics verdicts in the ledger Core agent principles and the one-way / two-way door rule (M9.30, M9.38); hard-lined goals retire (M9.29) Oct 20, 2026
…anch

CHANGELOG keeps both sides. ROADMAP M9.31 now names the dsh plugin
(aloop.mjs), whose PreToolUse list mirrors hooks.json by hand and so
lacks `mission doors hook`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012rsDo9GYzNx8tXucywZHnA
…M9.38)

The shell reader ends a $( … ) where bash does: here-strings, `<<` inside
$(( )), (( )), ${…} and $[…], `#` comments, case patterns' `)`, and two
heredocs on one line. close() is memoized so a deep nest stays linear, and
a nest past the recursion limit fails closed. A heredoc's delimiter is its
literal word, `]]` glued to an operator ends a test, and $((cmd)…) and
((cmd)…) fall back to a subshell as bash does.

The door gate reads a pipe into ( … ), the last stdin redirect, a python
that runs its stdin, /dev/stdin, `bash SCRIPT`, the aladdin and agentic
entry points, a ledger or run log reached through cd, a glob or
--in-place, npm -g with option values or install aliases, and a secret
value holding ` --help`. A branch created in the same command (`git switch
-c x && git push -u origin HEAD`) is not the default branch. A revoke that
only the run log has is compared with an authorization on the run log's
clock, so a ledger time pushed ahead by a fast clock cannot outrank it.

Each finding is a test: mission-doors (21 checks) and 15 new rows in
mission-scope-bash.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012rsDo9GYzNx8tXucywZHnA
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012rsDo9GYzNx8tXucywZHnA

# Conflicts:
#	CHANGELOG.md
#	docs/ROADMAP.md
#	plugin/bin/_mission.py
@adeelahmad
adeelahmad marked this pull request as ready for review October 20, 2026 10:18
@adeelahmad
adeelahmad merged commit f5fe254 into master Oct 20, 2026
5 checks passed
adeelahmad pushed a commit that referenced this pull request Oct 20, 2026
mission-doors.test.py (#86) made a sprint live by calling
record_authority() with no grant, which emitted plan.approved for any
plan. Since M0.9 plan.approved is only the human's typed approval of
that sprint's plan, so the test now records it the real way
(`events authority --sprint 1`, the human types the line it asks for).
ROADMAP: the door gate's live_run should read `events approval` too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WBVFNrEg85XAnSfHQidCXN
adeelahmad pushed a commit that referenced this pull request Oct 20, 2026
adeelahmad pushed a commit that referenced this pull request Oct 20, 2026
…er and rule fixes

The merge follows the #86 thread's recipe for _shellread.py (Word.body ours,
Cmd keeps end plus loop/pipe, their heredoc registration, `for NAME do`),
and doors.py skips the reader's compound markers and keeps a pipeline
element's cd in the element.

Round 4 (the verifiers of round 3): the scope gate walks every state a
command may leave the shell in. A cd inside an if/case/loop body, group or
function, or after && / ||, may or may not have run, so the rest is judged
from each dir (a compound's own `} > f` from where it started); a loop body
once per item; unquoted $VAR splits; eval and source /dev/stdin run in the
calling shell; a background job's cd stays in it; nesting past 8 levels,
more than 1024 walks or 50 000 judged commands blocks. find's -name tests
bound a removal through -o alternatives and negations; -fprint/-fls write;
wget --spider -O, npm -g --prefix, --global=false, os.environ.__setitem__/
update, xargs -i/-I{}, runuser options and `command -p export` are read.
Residuals are ROADMAP M9.54. mission-scope-bash.test.py: 718 commands.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0197nSutKR1QAAqgJhFZ8fPj
adeelahmad pushed a commit that referenced this pull request Oct 20, 2026
…tifact-gate fixes

Conflicts: CHANGELOG (master's lines, this track's M9.9 sub-bullet kept),
ROADMAP (master's rows; this track's M9.41/M9.42; its follow-up row moves
from M9.43, which master took, to M9.48). The track's new checks now get
the human's typed line for an approval (grant(), and the OpenCode test
types it through the UserPromptSubmit hook), and the ledger row that came
through git cites the human's words, as M9.17 requires.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0197nSutKR1QAAqgJhFZ8fPj
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

0