Skip to content

Releases: vstorm-co/agenticos

v0.0.343

Choose a tag to compare

@DEENUU1 DEENUU1 released this 28 Aug 12:19
0e09bb3

Changed

  • The org teardown's external cleanup is a durable Prefect deployment. It was
    deferred to an in-process spawn_after_commit task (#1137), which closed the
    commit-ordering window but left a durability gap: spawn_after_commit is not
    durable, so a process that died after the commit but before or during the cleanup
    lost it - orphaning a rag_<collection> table and its files with no record of what
    to drop. OrganizationService.purge still hands the work over after the commit, but
    hands over a run_deployment submission rather than the work: the run and its
    parameters - the paths and collection names, all that is left of the deleted rows -
    are recorded on the Prefect server, executed by a worker, and re-run by the flow's
    retries if that worker dies. New app/worker/tasks/teardown_tasks.py holds the
    idempotent cleanup, the durable flow wrapper and the submit-and-return dispatch; the
    flow re-checks each collection name against the knowledge-base table before dropping
    it, because the name is not tenant-unique (#913), so a name a second organization
    claimed between the commit and the drop keeps its table. The inline
    _purge_external_state and _collection_still_referenced go with it. (#1274)
  • The gap that remains is commit-to-dispatch: a crash before spawn_after_commit
    fires still loses the cleanup, which only a record committed with the delete - an
    outbox - would close. (#1274)

v0.0.342

Choose a tag to compare

@DEENUU1 DEENUU1 released this 28 Aug 12:00
9086c70

Fixed

  • LocalFileStorage.delete was async def over three blocking syscalls with no
    yield point
    - realpath inside _resolve_safe_path, Path.exists and
    Path.unlink. The RAG teardown loops (collection drop, knowledge-base delete) call it
    once per file with no bound, so dropping a large collection unlinked every upload in a
    single event-loop turn and stalled every other request the worker was serving. It
    runs through run_blocking now - the dedicated file pool load already uses - so
    every caller yields and the teardown loops interleave. This is the delete case #25
    did not reach: it offloaded save and load, and delete only became a hot path
    once the bulk teardown loops started calling it per file. Behaviour is unchanged; it
    still removes the file and tolerates a missing one. (#1294)

v0.0.341

Choose a tag to compare

@DEENUU1 DEENUU1 released this 28 Aug 11:57
8549273

Fixed

  • The card grids clipped by 34px on a 390px viewport.
    grid gap-3 md:grid-cols-2 xl:grid-cols-3 declares no column count below md, so
    the single implicit track is auto and sizes to its items' content: the grid box
    measured 324px while its own grid-template-columns computed 391.094px.
    min-width: 0 injected at every level of the ancestor chain changes nothing - the
    track is what is too wide, not the item - where grid-cols-1, that is
    repeat(1, minmax(0, 1fr)), makes the track the container's 324px and the card's own
    truncate finally has something to truncate against. Applied to fourteen grids
    across nine files: the ones whose cards carry user-supplied unbreakable text - a
    slug, an email, a URL, an id - because those are the ones whose min-content is
    unbounded. The sweep found 71 grids with the same shape and deliberately leaves the
    other 57: the pattern is only a defect when something inside cannot be broken, and a
    no-op class on 57 files is a diff nobody can review. (#120)
  • The chat control bar ran 27px past the composer at 390px. Three controls, 358px
    of them, in a justify-between row with the connection pill. The pill is shrink-0
    now - two words, nothing to give - and the control group min-w-0, which
    AgentPicker's trigger needed too: its max-w-[160px] on the name is a cap, not
    permission for a flex item to shrink. (#120)
  • A dashboard widget's info button was a 14x14 tap target, a third of the 44px both
    mobile platforms ask for. A before:absolute before:-inset-[15px] pseudo-element
    takes it to 44x44 without moving anything on screen. (#120)

Changed

  • #120 had "describe it later" in every field, so the issue body is now the audit:
    measured at 390x780 and 768x1024 in Chromium against the running app, fourteen pages,
    each scrolled to the end, recording overflow, tap-target size, text size and anything
    intersecting the fixed tab bar. Good news up front - the document never scrolls
    horizontally, at either width, on any of the fourteen pages. Three things are
    deliberately left and scoped on the issue rather than fixed here: the three data
    tables, which each sit in their own overflow-x-auto scroller so no column is lost
    but which want to be cards below md (Activity is 1155px in a 364px column); 45
    sub-40px tap targets, most of them the repository's own icon-sm, which want one
    hit-area token below md rather than bigger buttons; and the 10-11px mono label
    register, which is a design decision about a phone. (#120)

v0.0.340

Choose a tag to compare

@DEENUU1 DEENUU1 released this 28 Aug 11:52
6744797

Added

  • A README front page: hero, pitch, badges, nav, then the spec sample and what the
    product looks like.
    The content was already strong; what it lacked was the visual
    first impression. The graphics are authored in this repository - no icon package, no
    external asset host, no hand-copied path data:
    .github/assets/hero-{light,dark}.svg, one per theme and served through a
    <picture>, drawing the sentence the README opens with rather than decorating it -
    one spec, one runner, the surfaces that reach it and the four refusals underneath,
    in the app's own palette read out of globals.css and converted rather than
    eyeballed; and docs/assets/mark.svg, the same mark alone, which is now the docs
    site's logo and favicon, which the site did not have. One file, not two: an earlier
    draft had a copy in .github/assets/, which is the second-source defect this
    repository keeps citing. (#783)
  • Release and stars badges - the two the header lacked - plus a star-history image
    before the footer. Image paths are relative, so they render in a pull request as
    well as on main; absolute raw.githubusercontent URLs would show broken images to
    whoever reviews a change about graphics. (#783)
  • Three screenshots - the builder, the catalog, the chat surface - at 1600px,
    palette-reduced to 220 colours, 392KB for all three. Taken from a make dev run
    against a throwaway database rather than a developer's own, which held eleven E2E
    fixtures and four scratch agents; the fresh one was dropped afterwards and the
    developer's eleven agents verified still there. No shot shows a live model answer,
    deliberately: the alternatives were to spend somebody's tokens or to fake a
    transcript, so the chat shot is the composer with a real question typed and the agent
    picker showing which agent will answer. (#783)

Fixed

  • The docs table promised "Spec, version, exposure, run - the four nouns" where
    concepts.md has five: the trigger was added and one of the two pages updated.
    (#783)

v0.0.339

Choose a tag to compare

@DEENUU1 DEENUU1 released this 28 Aug 11:48
a84b85d

Fixed

  • /runs had 0px under its last run row where every other list page gets 64px. It
    was in FULL_HEIGHT_ROUTES, so PageTransition gave it min-h-0 and withheld
    PAGE_CLEARANCE - and it stopped being a full-height route in #914: the page's root
    is an ordinary scrolling flex flex-col and the run detail is sticky inside the
    page's own scroll rather than a pane with a scrollbar of its own. One regex was
    answering two different questions on the page's behalf, so it is two now:
    OWN_SCROLL_PANE (/chat alone, which needs the constrained chain so the transcript
    scrolls instead of the page) and OWN_BOTTOM_ROOM (/chat and /runs, both having
    something that must reach the bottom edge). Activity then declares PAGE_CLEARANCE
    one level in, on its list column - and that placement is the whole point, because
    padding on the box around the two-column row shortens the containing block the
    sticky panel is clamped to. Measured at 1440x800 against a transcription of the
    page's own chain: room on the outer box gives 64px of clearance and a panel top of
    -48px with its header cut off by 56px, which is the figure in the issue; room on
    the list column gives 64px and a panel pinned 8px from the window top, header
    visible. (#1206)
  • Below lg, the run detail panel's last 56px sat behind the mobile tab bar. The
    list column is hidden there and the panel is the only column, so its flat
    h-[calc(100dvh-1rem)] ran under a bar that is fixed bottom-0, min-h-[56px] plus
    the safe-area inset. Measured at 390x780: 56px hidden before, 8px clear after, which
    matches the 8px it already keeps at the top. The full height stays above lg, where
    the bar is hidden. Same element, two lines, so it is here rather than in a second
    change. (#1206)
  • page-transition.test.tsx was asserting the old reason - "constrains Activity too,
    where the list and the run detail scroll apart", true before the page was rebuilt and
    false since. It asserts both halves of what is true now: no min-h-0, and no pb-
    either, with the prefix-match case still checking that /runsomething gets the
    room. (#1206)

v0.0.338

Choose a tag to compare

@DEENUU1 DEENUU1 released this 28 Aug 11:35
e7b4c14

Changed

  • A presentation and correctness pass over every page of the site - 25 concept and
    reference pages, the 8 guides, the 3 reference stubs. The site was accurate and
    almost unreadable: 27 pages of unbroken prose, one mermaid diagram between them, no
    content tabs anywhere, ~20 pages with no callouts at all, and three flows drawn in
    ASCII that only line up in a monospace font. The words are mostly unchanged; what
    changes is what a reader sees before they start reading. 17 mermaid diagrams
    where prose or ASCII described a flow - the request path and the transaction's
    ordering, both ingestion pipelines, the three permission layers, park -> decide ->
    resume, the sandbox's three processes, envelope encryption, MCP's OAuth 2.1
    handshake, a sync's six stages. ~130 callouts, each promoting a rule the page
    already stated and whose violation costs something: a 2xx means the write is
    readable, a budget is checked before the request, an empty origin list allows
    nothing, the sandbox token is root-equivalent. Content tabs where alternatives were
    stacked vertically, and prose restructured where it was a table or a list in
    disguise. (#784)

Fixed

  • Four pages were teaching things that are not true here. patterns.md's three
    worked examples had all drifted off the code - a DI example injecting
    Depends(get_db) and Depends(get_current_user), neither of which exists, where the
    aliases do and DBSession's scope="function" is load-bearing (#353); a repository
    written as a ConversationRepository class, the one shape the architecture rule
    rules out; and a service holding self.repo, which no service in the codebase does.
    howto/customize-agent-prompt.md taught editing app/agents/prompts.py and
    overriding DEFAULT_SYSTEM_PROMPT, with a get_system_prompt_with_rag() and an
    AI_TEMPERATURE that do not exist - contradicting the sentence CLAUDE.md calls the
    whole design; it is rewritten around the spec and default_instructions.py.
    howto/add-background-task.md step 2 was asyncio.create_task(...), which is
    exactly the shape #417 was: the task starts before the request commits, so a flow
    reading its own row finds nothing, and the exception is dropped too - now
    spawn_after_commit / spawn. And howto/add-api-endpoint.md was a second,
    already-diverging copy of adding_features.md's walkthrough; it is the single copy
    now, with adding_features.md pointing at it. (#784)
  • Smaller corrections: configure-sync-sources.md named app/rag/connectors/ twice
    for a package that is app/services/rag/connectors/, and had a sentence ending in a
    colon with nothing after it; ROADMAP.md was dated 2026-07-27 and contradicted
    itself about the 100% gate, with three items describing features that have shipped;
    index.md promised "four nouns" where concepts.md has five; and a dead anchor in
    configuration.md, which mkdocs reports at INFO so --strict never caught it.
    (#784)

v0.0.337

Choose a tag to compare

@DEENUU1 DEENUU1 released this 28 Aug 11:32
8d3f08b

Added

  • A per-conversation approval mode in the chat. The spec decides which tools are
    gated, at publish time, per tool - which is right for a statement about what the
    agent is, and says nothing about the mood of one session. Somebody working through
    twenty turns with an agent that gates three tools answered the same three questions
    every turn, and their only way out was to republish the agent, changing it for
    everybody, permanently, to fix an afternoon. Three modes ride the send frame beside
    the model override: Follow the agent (the default, and exactly what existed
    before), Approve everything (standing consent for this conversation - every gated
    call granted without parking, each one still writing its row), and Ask about
    everything
    (gate every tool the agent can reach, including the ones the spec left
    ungated and the ones no capability owns). (#925)
  • Four things make it a session setting rather than a hole in the model. A caller who
    may not waive is refused, never downgraded - quietly following the spec would
    leave somebody believing they had turned the questions off, and the next parked run
    says the opposite; the check is in AgentRunnerService.prepare, the one funnel a
    fresh run and a resumed one share. Waiving needs approvals:decide and the
    organization's leave: a standing consent is the decision the queue exists to
    record, so organizations.chat_may_waive_approvals is the ceiling - off by
    default
    , changed by somebody holding approvals:decide - and without it a
    Builder's deliberate gate on send_email would be one click from nothing in every
    conversation. No channel still means no: only the web chat may waive, because
    ApprovalGate already refuses a run with nobody to ask. And every waived call is
    recorded
    - the row is written approved, names the consenting account and carries
    decided_via = "standing", its own column rather than a sentence in note, because
    a waived run indistinguishable from an agent that was never gated is
    docs/governance.md's trail quietly ceasing to be one. (#925)
  • "Ask about everything" gates MCP tools too. The spec-driven gate leaves them
    alone because their approval is a property of the connection; a person who does not
    trust an agent yet is asking about everything it can do. It only tightens, so it
    takes no permission, no ceiling and no surface check -
    ApprovalRequest.capability_id is nullable for exactly this case. (#925)
  • docs/governance.md gains How much one conversation wants to be asked; the tour
    gains chat-approval-mode.

Changed

  • New column organizations.chat_may_waive_approvals, default off, with its own switch
    beside the spending limit - so an upgrade changes nobody's behaviour and the waive
    option does not render until an owner turns it on.

v0.0.336

Choose a tag to compare

@DEENUU1 DEENUU1 released this 28 Aug 11:04
3938c40

Added

  • An Owner column on the workspaces table. It said who else could see a
    workspace and never who it belongs to: access_label describes the scope -
    "everybody who talks to this agent", "one person" - which is a different fact, and on
    an agent-scoped workspace shared by six people it is not the one an operator is
    asking. owner_label was already on the row and already rendered by the chat panel,
    used here only as a fallback heading. Plain text and never a link, because
    owner_ref is a string and a Slack-sourced workspace's owner is a platform id
    rather than an account (#131), so a linked cell would be broken on half the rows.
    Sortable, because grouping a deployment by holder is what somebody opens this to do.
    (#137)

Changed

  • One folder tree, not two. /skills and /workspaces/{id} had written the same
    tree twice - the same recursion, the same expand-collapse set keyed on a folder's
    path, the same chevron and two folder icons, the same role="tree" with
    aria-expanded - over two node shapes and two polarities of open state, one
    holding what was collapsed and the other what was open. PathTree in
    components/files is now the mechanics and the semantics: indentation by depth, the
    roles, one selected file, the open set. What a row says stays with the caller,
    because a skill's file is a name and a workspace's is a name, a size and a download -
    which is why there are two render props: renderFile inside the button that opens
    the file, so that is all a screen reader announces, and renderFileMeta beside it,
    because the workspace's download must not need the file opened first and a button
    inside a button is invalid. workspace-explorer.tsx is 110 lines lighter,
    skill-files.tsx 80, against one 203-line component. One deliberate visual change: a
    skill's file rows were indented twelve pixels further than its folders and the
    workspace's were not, so the two trees disagreed about the same question. They indent
    alike now. (#137)

v0.0.335

Choose a tag to compare

@DEENUU1 DEENUU1 released this 28 Aug 11:01
b5a5aad

Changed

  • The Share conversation dialog picks a person rather than asking for an email
    address.
    It had a text field and a hand-rolled suggestion list that appeared only
    once something had been typed, so the control's default state was a blank box you
    had to already know the answer to fill, and every mistyped address was a 404.
    MemberPicker - a popover over a cmdk list - opens with the organization in it,
    each row a face and a name over the address, and somebody who already has access is
    not offered again. The API has always accepted shared_with beside
    shared_with_email, so this is a client change rather than a contract change, and
    sharing outside the organization becomes impossible by construction - #930's client
    half. matchingMembers and its four tests go with the field. (#931)
  • View and Edit carry icons and a sentence. Eye and Pencil, in the select and
    on every row, with one line saying what the level permits - because "edit" on a
    conversation is not obvious: it is rename, archive, delete and append turns, which
    ConversationService._may_write decides and nothing on the dialog used to say.
    (#931)
  • The access list reads as people. MemberIdentity, the same row the members
    table and the alerts picker draw, resolved against the organization's members - with
    a fallback to whatever the share itself holds, because a share whose member is gone
    still has to be revocable. The level is the catalog's word now: the badge printed the
    API's raw view/edit, so that one row was English in every locale while the select
    above it was translated, and the i18n guard could not see it because it is an
    expression rather than a literal. (#931)
  • DIALOG_FORM instead of DIALOG_CONFIRM, and three separated sections - invite, who
    has access, the link - rather than three controls on one row. A share token is not a
    person, so it keeps its own row. (#931)

v0.0.334

Choose a tag to compare

@DEENUU1 DEENUU1 released this 28 Aug 10:58
69ea559

Fixed

  • Every notification link was organization-agnostic, and the page it opens acts on
    whichever organization the reader last used.
    apiClient stamps
    X-Organization-Id from a selection persisted per browser, so somebody in two
    organizations who was last working in Globex opened the approval alert for a run in
    Acme and read Globex's queue: very likely empty, and reading as nothing is
    waiting
    about a run that is parked and ageing towards
    ApprovalService.expire_stale. The agent links were wrong more quietly -
    /agents/{id} under the wrong organization is a refusal for an agent the reader can
    genuinely see, one switch away. run.organization_id and agent.organization_id
    were in scope at all four call sites and discarded. Every link now carries
    org=<id>, built in one place - NotificationService._link, which picks the
    separator from the path because the approvals link already carries
    ?tab=approvals. (#1204)
  • The console adopts it the way it adopts /orgs/{id}. organizationInQuery
    reads it under the same two rules as the path's reader and for the same reasons
    #1032 gives: a UUID only, so a future ?org=new is not adopted as a tenant id and
    refused on every request, and lower-cased, because the value is stored and found by
    === against ids the server serialises in canonical lower case. The adoption is the
    existing layout effect in the recovery hook, before the tenant cache reset and
    before the page's own queries, so the first request the page makes already carries
    the right tenant. Two rules follow from what the parameter is: the path outranks
    it
    , since /orgs/{id} is that organization while ?org= only says which one an
    alert was about; and adoption is keyed on the path and the adopted id together,
    because two alerts about two organizations arrive at the same path and keying on the
    path alone would read the first one's tenant. (#1204)
  • A reader who has since left that organization is told the link is the reason, rather
    than being moved in silence and reading another organization's page as the answer to
    the alert. It cannot name the organization: they are not a member, so it is not in
    their list. (#1204)

Added

  • docs/governance.md gains Every link says which organization it is about under
    Alerts.