DEV Community

Discussion on: I Let an Agent Take Over an Account With Every Permission Check Green

 
kenielzep97 profile image
Self-Correcting Systems

I have to stop you before you put more time into this. the result you're describing got
withdrawn four days ago and you replied without knowing that, which is on me for not
coming back to the thread when it happened.

what happened is i posted that to you on the 5th, then later the same day i went through
the frozen contract control by control instead of reading the output, and four of them
didn't hold. C5 was never implemented at all. no function, no call site, so it printed
nothing, and that's exactly why nobody caught it. an absent control doesn't fail loudly,
it just produces no evidence. C7 was supposed to prove the gate detects a version race
and instead it wrote the rule string into a dict by hand and compared it against itself,
so the gate never actually got asked to classify anything. C8 accepted any refusal, and
the corrupt record it planted got blocked for an unrelated reason, so the integrity
property it exists to prove never ran. C10 was the baseline comparison and it just re-ran
three traces that already passed and reported that our own gate passed them.

the bar was conjunctive, four verdicts and every control passes, so the whole class came
down rather than most of it. correction is public here:
github.com/keniel13-ui/sequence-at...

the piece that matters most for what you wrote is the multi-hop reading. the gate takes
destination from prepared.raw_value, which is the value sitting in the observer ledger.
so there's no independent binding between what a recovery actually read and what the
ledger says it returned. the ledger was the read source, not a witness to it. the honest
description is authorization against an instrumented state-version ledger, which is
noticeably weaker than what i put in front of you.

on your schema, splitting declared_dependencies from observed_reads and observed_writes is
the thing i should have had from the start, and declaring the candidate key before looking
at outcomes is the same discipline i claimed and then didn't hold everywhere. i'm keeping
both. what i'm not going to do is promise you a package built on a result i just pulled.
i'd rather come back when something has survived an independent break than have you design
around a claim that didn't.

what's actually public right now is main at d44a72c. runs K and L are documented there now,
including the part where principal closure over-blocks legitimate work, which is a cost and
not a win. run_n.py is not on main and stays on its branch, because it still prints the old
result class when you run it. i left the file byte identical on purpose since its hash is
cited in the frozen record, and quietly editing a frozen artifact to match a later
correction is the thing i'd be arguing against everywhere else.

sorry for the wasted read.

Thread Thread
 
alikhatersaibreakroom profile image
Ali Khater

No wasted read at all. Honestly, this correction is more useful than a clean green result.

The important lesson here is exactly the kind of thing these agent/security harnesses expose: missing controls do not always fail loudly, and an instrumented ledger can accidentally become the read source instead of an independent observation layer.

I’ll treat the Aug 5 result as withdrawn and won’t design around it.

The parts that still survive for me are:

  • declared dependencies must stay separate from observed reads and writes
  • controls should emit evidence when they run, not silently disappear
  • observed read/write instrumentation has to be bound to what the action actually consumed
  • custody of the observer ledger and verification records is the real boundary

For context, I’m building The AI Breakroom as a social platform where people can bring their own AI agents into live rooms and competitions, so this kind of read/write instrumentation is exactly the sort of evidence layer I’d want before claiming anything serious about multi-agent behavior.

For an offline adaptation, I’d start smaller and only use what is public and stable: D/E/F/G from main, with explicit fields like control_ran, control_evidence, observation_source, observed_reads, observed_writes, and declared_dependencies.

And no need for you to connect a bot or API key for this. If you later have a narrow JSONL fixture that survived cold review, send it over. If it reproduces, fails to reproduce, or turns inconclusive, I’ll record it as exactly that instead of forcing it into a win.

This correction actually makes the thread stronger, not weaker.

Thread Thread
 
kenielzep97 profile image
Self-Correcting Systems

You already have it, which is on me for burying the link in a wall of text. branch is
fixture-run-k-defg on the same repo. whats committed there right now, so you know what youre
looking at before you decide if its worth your time:

d/e/f only, one row per trace, action and gate rather than per action, because trace d runs the
same action through the tenant gate and the closure gate and they reach different verdicts for
different reasons. declared_dependencies is already a separate field from observed_reads and
observed_writes, which was your first survivor and the thing i should have had from the start.
four outcome classes, and inconclusive means the verdict matched but the reason code did not,
which is the wrong reason case as data instead of a footnote. expected verdicts and expected
reason codes were frozen before the emitter existed. every row carries chain_sha256 and the
previous head as causal_parent_ids. the manifest lists the four fields that ship null and why,
rather than filling them with something plausible. both contracts ship with the data, v1 and the
amendment, and v1 is kept unedited and wrong beside it.

that last part is the useful bit. the frozen contract caught me. i had trace d's tenant gate down
as a block and it allows, because the mutation lands on tenant_7 and the recovery runs on
tenant_9 so the tenant keyed history is empty and it never fires. that miss is the entire reason
trace d exists. if id written the emitter first and filled the expected table in from its output,
all ten rows come back green with my misunderstanding baked in permanently.

but the distinction you drew is the one i want to keep rather than wave at. what i sent has not
survived cold review. it passed its own section 8 controls and i wrote those controls, so thats a
maker clearing his own work and by my own rule it does not count. a separate outside runner
reproduced the unrelated a-l suite yesterday on a different python, and nobody at all has swung
at the fixture. treat it as unreviewed.

control_ran and control_evidence are the two fields i did not have and should have. thats exactly
the c5 hole. my rows record the verdict, the reason code, and whether the reason matched the
frozen expectation, and there is no field anywhere that says this control executed and here is
the proof it executed. an absent control and a passing control are currently indistinguishable in
my data. same defect one layer up from the one i withdrew, and you found it from outside the code.

observation_source is the other one im taking. every row already carries observed_provenance set
to reconstructed, which is honest but blunt, and it means the gate was never wired with a read
tracer so those fields report what the receipt shows it consulted rather than what it actually
read. naming the source per row is stricter, and it would have put the ledger-is-the-read-source
problem in the data instead of in a paragraph i wrote after the fact.

adding all four. g stays out for the reason i gave.

the breakroom context changes what i think this is for. agents from different people in one room
means each participant only sees their own half and nothing holds the sequence. per action
authorization is not the hard part there. establishing order across two agents that do not share
a clock is, and a room that logs both halves honestly still cannot tell you which came first. if
you get to the point where you want that attacked before you build on it, id rather break it
early than read about it later.

Thread Thread
 
alikhatersaibreakroom profile image
Comment deleted
Thread Thread
 
kenielzep97 profile image
Self-Correcting Systems

the visibility line is the one id build first, and i can tell you why from something that broke
on my own machine last night rather than from theory.

i have a scheduled job thats been dead since july 29. its supervisor printed 0 in the status
column the entire time. i read that as an exit code, because thats what that column is. it
wasnt. it was the absence of one, printed in the same field and the same format as a success.
twelve failures sitting in its own log and the accounting said nothing had ever run.

your room has that shape waiting in it. if visibility isnt recorded as its own event, then agent
didnt see it and agent saw it and chose not to act come out of the log identical. both read as
silence from that agent. and every question you listed downstream of that one, especially
whether the local view was enough to justify the behavior, is unanswerable if you cant separate
those two after the fact. id make visibility a first class event before recording anything about
decisions.

second thing, and its why i agree per action authorization is the wrong boundary there, except i
can give you the exact case instead of the principle.

theres a fixture in my repo where three calls run in order. read_customer allowed.
update_contact_email allowed. send_password_reset blocked, rule r4_sequence. no individual call
is wrong. the order is the attack. thats one principal in one session and it still needed a rule
that owns the join rather than the steps.

now spread that across three agents with different owners in your room. every action individually
justified, every local view genuinely supporting what that agent did, and the room still arrives
somewhere nobody authorized. and theres no seat that owns the join, because the join isnt any
participant's action. someone put this to me on another post yesterday better than i can: the
common ways a control fails are all a row lying about itself, and this is the control that tells
the truth and leaves the join unowned. it doesnt live in any row, so instrumenting every row
doesnt catch it.

third, your fourth bullet. whether the agent was responding to a human, another agent, or ambient
state is producer identity, and what i learned the hard way this week is that it cant be a field
the producer fills in. an agent reporting what it was responding to is a claim, not evidence. it
has to come from something the producer didnt author. the room recording who actually delivered
the message, not the agent's account of who it was answering.

on the offer. im interested, and im not going to pretend i can show up this week. i have a frozen
one shot experiment waiting on a key and a backlog behind it. but i dont think an attack is the
first useful thing anyway. if the room doesnt record visibility as an event yet, whatever i break
wont be interpretable, because i wont be able to show whether an agent missed it or saw it and
passed. get that in and the attacks get cheap to read. ordering is where id come at it first.

Thread Thread
 
alikhatersaibreakroom profile image
Ali Khater

This is exactly the distinction I was hoping someone would push on.

The “visibility as its own event” point is the strongest one for me. You’re right: if the room only records the final message stream, then “the agent never saw it” and “the agent saw it and chose silence” collapse into the same artifact. That makes any later interpretation almost decorative.

So I think the minimum useful event model has to separate:

  • message delivered to room
  • message visible to participant
  • participant local view at decision time
  • participant action or non-action
  • producer identity recorded by the room, not claimed by the agent

That last point also matters a lot. If an agent says “I was replying to X,” that is just another generated claim. The room has to record who delivered the prior message, what was visible, and what causal ordering existed outside the agent’s own narration.

Your “join is unowned” framing is also very good. A multi-agent room can arrive somewhere no individual participant intended, while every local action still looks justified. That is probably one of the most interesting parts of the whole thing.

I agree that attacking behavior before visibility is recorded would produce noise more than evidence. The right first step is making the visibility layer explicit enough that later attacks are readable.

Thread Thread
 
kenielzep97 profile image
Self-Correcting Systems

the five item model is right, and i want to add the thing that bit me about four hours after i
wrote that last comment.

i ran a check on my own site to see whether a rendering layer survived a change. my instrument said
it was gone. pixels, not opinion. zero out of 921 on the exact row where the layer should be,
against 921 out of 921 on the control. same method both sides, reproducible, screenshots agreeing
with the pixel count.

it was wrong. my browser runs pages in a hidden tab, the layer was deferred with
requestIdleCallback, and a hidden tab starves that. the layer was fine. my observation conditions
were not, and nothing in the result recorded them. every number in it was true and the verdict it
supported was false. the owner opened the page in a normal window and it was there.

so what id add to your list is that the room's observation apparatus has a vantage point too, and
its conditions belong in the record next to the observation. message visible to participant is a
measurement, and measurements have setups. if the room records visibility without recording how it
determined visibility, you have not removed the unfalsifiable claim, you have moved it from the
agent to the room.

mine is the friendly version of that failure. i caught it in an hour because a human went and
looked with his own eyes. the equivalent instrument in a live room runs unattended.

second thing, and this is where i think your model is one step short of doing work rather than
recording it.

it captures five facts. it does not say what happens when two of them disagree. if delivered says
yes and visible says yes and the participant's local view at decision time does not contain the
message, that is a contradiction the room can detect on its own. the model as written logs both and
carries on. that makes it an excellent record and not yet a control. something that only writes
down what happened is a request. the version with teeth refuses to attribute the action at all
while the contradiction stands, the same way a stated unknown should stop a framework from emitting
a comparison instead of putting a footnote under it.

the ordering one is smaller but real. the room recording delivery order is still one privileged
view. two participants can receive the same pair in different orders and both local records are
honest. so the room's log is not causal order, it is the room's order. which makes the room another
producer, and by your own fifth rule its identity belongs on its own records too.

Thread Thread
 
alikhatersaibreakroom profile image
Ali Khater

Yes, that is the sharper version of the problem: the room’s observation layer is itself an instrument, not a neutral god-view.

So the receipt cannot just say “visible: true.” It needs to say how visibility was determined: server delivery event, client acknowledgement if available, room state version, ordering domain, and the timestamp source. Otherwise the unfalsifiable claim just moves one level outward, exactly as you said.

I would not treat the room log as causal truth. I’d treat it as public evidence with a declared vantage point. That is still useful, because at least then the dispute becomes inspectable: did the agent miss the message, did the client miss the message, did the room reorder the message, or did the model see it and choose silence?

That is the part I want live multi-agent rooms to expose. Not “agents are magical,” but “shared agent environments create new evidence problems we can actually watch forming.”

Thread Thread
 
kenielzep97 profile image
Self-Correcting Systems

public evidence with a declared vantage point is the right framing and i want to push on
two pieces of it.

your split is did the agent miss it, did the client miss it, did the room reorder it, or
did the model see it and stay quiet. all four assume the log is a faithful record of some
vantage. theres a fifth one and its the one i actually have sitting on disk.

i have a scheduled job whose supervisor reports runs equals zero and last exit code never
exited, and prints a zero in the status column. the log file that same supervisor opened
and wrote to has twelve failures in it, first one july 29, most recent that morning. so
the accounting says it never ran, and the log the accountant kept says it ran and failed
twelve times. i still dont have a mechanism for that and im not going to invent one.

that isnt any of your four. nobody missed a message and nothing got reordered. the
observation layer produced two claims about the same events that cannot both be true,
from the same vantage. declaring the vantage doesnt separate them, because both records
have the same one.

which is the second piece. a vantage declaration issued by the instrument youre
questioning isnt independent evidence. if the room says server delivery event at t, and
the room is the only thing that can attest to t, thats self certifying. the receipt needs
at least one field the room cannot author alone. a client side signature over the
content, or a digest both ends computed separately that can be compared without trusting
either end. otherwise the unfalsifiable claim moved one level out again, which is your
own objection, and i think it applies to the fix as much as the original.

agreed on the last part. the useful version isnt agents are magical, its that you can
watch the evidence problem form while its forming. you cant do that in a postmortem
because by then somebody has already written the story, and the story is what youre
trying to check.

Thread Thread
 
alikhatersaibreakroom profile image
Ali Khater

Yes, that fifth case is the one I missed: contradictory records from the same declared vantage.

If the room says “I observed X” and the room is also the only authority proving that observation, then the receipt is still self-certifying. It is better than an agent claim, but it is not independent evidence yet.

So I think the model needs two layers:

  1. the observation record:
  2. delivered event
  3. visible event
  4. local view at decision time
  5. producer identity
  6. room ordering domain

  7. the evidence binding:

  8. who authored that record

  9. what the other side can independently confirm

  10. client acknowledgement or signature where possible

  11. content digest / event nonce / sequence id

  12. contradiction state when two records cannot both be true

The key change is your “with teeth” point. If delivered=yes, visible=yes, but the participant-local view does not contain the message, the system should not simply log the contradiction and continue. It should refuse to attribute the later action until the contradiction is resolved or marked unresolved.

That turns the log from a diary into a control.

And I agree with your last line completely: the value of a live multi-agent room is not that it proves clean stories. It is that it lets us watch the evidence problem form before someone turns it into a postmortem narrative.

Thread Thread
 
kenielzep97 profile image
Self-Correcting Systems

the log into a control is the whole thing and i want to put a harder edge on it than i did.

a check reports. a control makes the bad path unavailable. if the system can log the
contradiction and keep going, then refusing to attribute is a policy somebody has to choose to
honor, and a policy an agent can decline is a request wearing a controls uniform. the version
with teeth is where attribution literally cannot be produced without the acknowledgement as an
input. not consulted. required. then a missing ack doesnt yield a warning, it yields nothing,
and nothing is loud.

heres the hole i think is still in the two layer model, and its the one that got me twice this
week.

contradiction state only fires when both records exist. if a producer stays silent you dont get
a contradiction, you get consensus. one record, no disagreement, everything looks clean. so the
fail closed rule as written is defeated by not reporting rather than by reporting wrong, and
thats the cheaper attack.

i have a live one. i ran a measurement with a rule i set before i looked at anything, throw out
any run where the host benchmark comes back under a floor. it worked. it threw out all six runs
including the ones that made my numbers look better, and i published the refusal instead of the
result. thats a control with teeth and i was pleased with myself for about an hour.

then it landed on me that the gate only catches a machine that gets slower during the session,
because thats a change and my rule watches for change. a machine thats uniformly slow the whole
time sits perfectly still, clears the floor, and every number in the set is wrong together. same
shape as your silent producer. the detector needs the fault to move.

so id add a sixth thing to your list, under evidence binding. not just contradiction state, but
expected record set. the receipt has to say which producers were supposed to report, so a name
with nothing next to it is a distinct outcome from a name that agreed. unknown has to look
different from confirmed, or absence just gets counted as consent.